[NOI2014]魔法森林

Posted by Panda2134's Blog on January 18, 2018

链接:Luogu-P2387

这是我做的第一道非模板LCT题目……自己并没有想出来,看题解也看了好久,于是总结下做这个题目的思路。

题意

给出一个$n$个点,$m$条边的无向图,每条边都有权值$a_i,b_i$,求一条从点$1$到点$n$的路径,使得这条路径上边的$a_i,b_i$最大值之和最小。$2 \leq n \leq 5 \times 10^4, 0 \leq m \leq 1 \times 10^5$。

思路

二分?

看到最大值最小,想到二分。怎么二分呢?如果是只有一种权值的情况,可以二分+BFS,不过我们有更好的方法,也就是瓶颈生成树。也就是说这里要求出2个参数的瓶颈生成树。对于有2种权值的情况,是否也可以类似地直接贪心呢?比如说,按照$a_i+b_i$排序?听上去就不靠谱。或者说先二分$a_i$,对每个$a_i$的值去二分$b_i$? 这也不符合“和的最大值最小”这个要求。

生成树!

不过,这个做法倒是提供了一些思路。我们也许可以枚举$\max\{a_i\}$,对每个枚举出来的$\max\{a_i\}$去判断如何加边。 暴力枚举复杂度太高。考虑效仿Kruskal算法,把边按照$a_i$排序,然后再枚举当前考虑第几条边。这样的话每次判断是否加入当前边即可。判断的依据是什么?是否成环,环中最大边是哪条。根据生成树的回路性质,某个回路中的权值最大边恰好有一条不在最小生成树中。于是每次加边(相当于枚举了$a_i$),同时对$b_i$做动态加边最小生成树,再用$a_i$与当前的$1 \rightarrow n$路径上的$\max\{b_i\}$之和更新答案。显然对于每个给定的$a_i$,$b_i$满足最小生成树性质时一定最优。而我们枚举了$a_i$的取值,这样一定可以遍历所有的可能情况。

细节的处理

上面的方法似乎很正确,不过好像有点问题:加入的边一定在$1 \rightarrow n$的路径上吗?

稍微想想就会发现,就算加的边和这条路径无关,也没有关系:由于这时$a_i$大于$1 \rightarrow n$的路径上$a_i$最大值,而$1 \rightarrow n$的路径上$b_i$最大值又不变,于是在考虑$1 \rightarrow n$的路径上$a_i$最大值时,答案已经松弛过了,此时不能再松弛答案!如果当前边与$1 \rightarrow n$的路径上某个边构成环,那么这次松弛也是有用的,因为对于不同的$\max\{a_i\}$,$b_i$取值也相应不同。

实现

考虑下求出“生成树路径边权最大值”怎么做到:用Link-Cut Tree即可。通过一次$\text{MakeRoot}$和一次$\text{Access}$,就可以把路径弄到一条$\text{Preferred Path}$上。然后在Splay里面打标记就好了。注意边权的处理:给每条边单独建一个点就好了。

代码如下:

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 5e4, MAXM = 1e5;

//----------------LCT--------------------
struct Node *null, *nd[MAXN+10];
struct Node {
	int v, s, maxv; bool flip;
	Node *fa, *ch[2];
	Node() { v = maxv = 0; s = 1; flip = false; fa = ch[0] = ch[1] = null; }
	Node(int v_) { v = maxv = v_; s = 1; flip = false; fa = ch[0] = ch[1] = null; }
	bool splayrt() { return fa->ch[0]!=this && fa->ch[1]!=this; }
	int rel() { return splayrt() ? -1 : (fa->ch[0]==this ? 0 : 1); }
	void rev() { flip^=1; }
	int cmp(int k) { return k == ch[0]->s + 1 ? -1 : (k > ch[0]->s + 1 ? 1 : 0); }
	void pushdown() {
		if(flip) {
			flip^=1; ch[0]->flip^=1; ch[1]->flip^=1;
			swap(ch[0], ch[1]);
		}
	}
	void maintain() {
		maxv = max(max(ch[0]->maxv, ch[1]->maxv), v);
		s = ch[0]->s + ch[1]->s + 1;
	}
};

void init_null() {
	null = new Node(0); null->s = 0;
}

void rotate(Node* o) {
	Node *x, *y, *k; int d, d2;
	if(o->splayrt()) return;
	x = o->fa; y = x->fa;
	d = o->rel(); d2 = x->rel();
	k = o->ch[d^1];
	if(!x->splayrt()) y->ch[d2] = o;
	o->fa = y;
	o->ch[d^1] = x; x->fa = o;
	x->ch[d] = k; k->fa = x;
	x->maintain(); o->maintain();
}

void Splay(Node* o) {
	static Node *x, *S[(MAXN<<1)+10]; int p, d, d2;
	for(p=1, S[p] = o; !S[p]->splayrt(); p++)
		S[p+1] = S[p]->fa;
	for(; p; p--) S[p]->pushdown();
	while(!o->splayrt()) {
		x = o->fa; 
		d = o->rel(); d2 = x->rel();
		if(!x->splayrt()) {
			if(d == d2) rotate(x);
			else rotate(o);
		}
		rotate(o);
	}
}

Node* FindMax(Node* o) {
	if(o->v == o->maxv) //!!
		return o;
	else return o->ch[0]->maxv == o->maxv ? 
		FindMax(o->ch[0]) : FindMax(o->ch[1]);
}

Node* Kth(Node* o, int k) {
	if(o == null) return o;
	int d = o->cmp(k);
	if(d==-1) return o;
	return Kth(o->ch[d], k - d*(o->ch[0]->s + 1));
}

void Access(Node* o) {
	for(Node *t=null; o!=null; t=o, o=o->fa) {
		Splay(o); o->ch[1] = t; o->maintain();
	}
}

void MakeRoot(Node* o) {
	Access(o); Splay(o); o->rev();
}

Node* GetRoot(Node* o) {
	Access(o); Splay(o);
	while(o->ch[0] != null) o = o->ch[0];
	return o;
}

void Link(Node* u, Node* v) {
	if(GetRoot(u) == GetRoot(v)) return;
	MakeRoot(u); Splay(u); u->fa = v;
}

void Cut(Node* u, Node* v) {
	if(GetRoot(u) != GetRoot(v)) return;
	MakeRoot(u); Access(v); Splay(u);
	if(u->ch[1] == v) {
		u->ch[1] = null; v->fa = null;
		u->maintain();
	}
}

//-----------------------------------------

struct Edge {
	int u, v, a, b;
	Edge() {}
	Edge(int u_, int v_, int a_, int b_):
		u(u_), v(v_), a(a_), b(b_) {}
	inline bool operator<(const Edge& rhs) const
	{ return a < rhs.a; }
} E[MAXM+10];
int N, M, Ans, fa[MAXN+10];

int GetFather(int x) { return fa[x] == x ? x : fa[x] = GetFather(fa[x]); }
inline void Union(int x, int y) { fa[GetFather(x)] = GetFather(y); }

template<typename T>
inline void readint(T& x) {
	T f=1, r=0; char c=getchar();
	while(!isdigit(c)){ if(c=='-')f=-1; c=getchar(); }
	while(isdigit(c)){ r=r*10+c-'0'; c=getchar(); }
	x = f*r;
}

void Init() {
	int u, v, a, b;
	init_null(); 
	readint(N); readint(M);
	for(int i=1; i<=N; i++) {
		nd[i] = new Node(0);
		fa[i] = i;
	}
	for(int i=1; i<=M; i++) {
		readint(u); readint(v);
		readint(a); readint(b);
		E[i] = Edge(u, v, a, b);
	}
	sort(E+1, E+M+1);
}

inline void AddTreeEdge(int u, int v, int b) {
	Node* w = new Node(b);
	Link(nd[u], w); Link(nd[v], w);
	if(GetFather(u) != GetFather(v)) 
		Union(u, v);
}

inline Node* GetMaxEdge(int u, int v) {
	Node* w;
	MakeRoot(nd[u]); Access(nd[v]); Splay(nd[u]);
	w = FindMax(nd[u]); 
	Splay(w);
	return w;
}

inline void CutMaxEdge(int u, int v) {
	Node *w, *x, *y; int k;
	w = GetMaxEdge(u, v); k = w->ch[0]->s + 1;
	x = Kth(w, k-1); y = Kth(w, k+1);
	if(x == null || y == null) return;
	Cut(w, x); Cut(w, y);
}

void Work() {
	int u, v, a, b; bool add = false;
	Ans = INT_MAX;
	for(int t=1; t<=M; t++) {
		u = E[t].u, v = E[t].v, a = E[t].a, b = E[t].b;
		add = false;
		if(GetFather(u) != GetFather(v)) 
			add = true, AddTreeEdge(u, v, b);
		else if(b < GetMaxEdge(u, v)->v) {
			add = true;
			CutMaxEdge(u, v);
			AddTreeEdge(u, v, b);
		}
		//更新答案
		if(add && GetFather(1) == GetFather(N)) 
			Ans = min(Ans, a + GetMaxEdge(1, N)->v);
	}
	if(Ans < INT_MAX)
		printf("%d", Ans);
	else puts("-1");
}

int main() {
	Init(); Work();
	return 0;
}