魔法森林[NOI2014]

【题目描述】

题目描述又臭又长

简单来说 有一个有\(n\)个点,\(m\)条边的无向图,每条边有两个权值\(a,b\),从点\(u\)走到任意一个点\(v\)的代价是途经的边中最大的\(a\)加上最大的\(b\)

求从点\(1\)到点\(n\)的最小代价

【输入格式】

rt,给你一张图

【输出格式】

输出最小代价。如果无法到达,输出-1

题解

如果边权只有一个,那么显然就是建出最小生成树

但是这里有两个 我们可以先以\(a\)为关键字给所有边从小到大排序 然后依次把每条边加入

这里就和最小生成树有些异曲同工之妙相似

假设加入的这条边是\((u,v)\),如果\(u,v\)还未联通就直接给它连上

如果已经联通了 我们就检查一下现在的\(u\rightarrow v\)这条链上面\(b\)最大的一条边 如果那条边的\(b\)\((u,v)\)\(b\)大 那就把那条边割掉 把\((u,v)\)这条边给连上

这个就可以用LCT来维护

\(b\)最大的边只需要在splay上维护一下 然后split(u,v)就能找到

每次加完边后 如果\(1\)\(n\)联通 就更新一下答案

\(1\rightarrow n\)路径上最大的\(b\)已知 那最大的\(a\)呢?

最大的\(a\)直接用这次加入的边的\(a\)即可

因为如果这次加入的边不在\(1\rightarrow n\)的路径上 那么在这条路径第一次在树上出现时就应该已经用正确的\(a\)更新过了

复杂度\(O(n\log n)\)

这道题可以不用findroot来判联通。。。但是懒得写了

代码

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

inline int read() {
	int x = 0, f = 1; char ch = getchar();
	for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') f = -1;
	for (; ch <= '9' && ch >= '0'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ '0');
	return x * f;
}

struct edge{
	int u, v, a, b;
	
	friend bool operator < (edge x, edge y) {
		return x.a < y.a;
	}
} e[N];

int ch[N][2], fa[N], mx[N], val[N], tag[N];

#define lson ch[x][0]
#define rson ch[x][1]

inline void pushup(int x) {
	mx[x] = x;
	if (val[mx[lson]] > val[mx[x]]) mx[x] = mx[lson];
	if (val[mx[rson]] > val[mx[x]]) mx[x] = mx[rson];
}

inline void rev(int x) {
	swap(lson, rson);
	tag[x] ^= 1;
}

inline void pushdown(int x) {
	if (tag[x]) {
		if (lson) rev(lson);
		if (rson) rev(rson);
		tag[x] = 0;
	}
}

inline bool isroot(int x) {
	return ch[fa[x]][0] != x && ch[fa[x]][1] != x;
}

inline void rotate(int x) {
	int y = fa[x], z = fa[y], k = (ch[y][1] == x);
	if (!isroot(y)) ch[z][ch[z][1]==y] = x;
	fa[x] = z;
	ch[y][k] = ch[x][k^1]; fa[ch[x][k^1]] = y;
	ch[x][k^1] = y; fa[y] = x; 
	pushup(y); pushup(x);
}

int q[N], top;

inline void splay(int x) {
	q[top=1] = x;
	for (int i = x; !isroot(i); i = fa[i]) q[++top] = fa[i];
	while (top) pushdown(q[top--]);
	while (!isroot(x)) {
		int y = fa[x], z = fa[y];
		if (!isroot(y)) {
			((ch[z][0] == y) ^ (ch[y][0] == x)) ? rotate(x) : rotate(y);
		}
		rotate(x);
	}
}

inline void access(int x) {
	for (int i = 0; x; i = x, x = fa[x]) {
		splay(x), ch[x][1] = i, pushup(x);
	}
} 

inline void makeroot(int x) {
	access(x), splay(x), rev(x);
}

inline void link(int x, int y) {
	makeroot(x); 
	fa[x] = y;
} 

inline void split(int x, int y) {
	makeroot(x), access(y), splay(y);
}

inline int findroot(int x) {
	access(x), splay(x);
	while (lson) pushdown(x), x = lson;
	splay(x);
	return x;
} 

inline void cut(int x, int y) {
	split(x, y);
	if (ch[y][0] == x && !ch[x][1]) {
		ch[y][0] = fa[x] = 0;
	}
}

int n, m, ans;

int main() {
	n = read(), m = read();
	ans = 0x7fffffff;
	for (int i = 1; i <= m; i++) {
		e[i].u = read(), e[i].v = read();
		e[i].a = read(), e[i].b = read();
	}
	sort(e + 1, e + m + 1);
	for (int i = 1; i <= m; i++) {
		val[n + i] = e[i].b;
	}
	for (int i = 1; i <= m; i++) {
		bool lk = 1;
		if (findroot(e[i].u) == findroot(e[i].v)) {
			split(e[i].u, e[i].v);
			int mxnow = mx[e[i].v];
			if (val[mxnow] > e[i].b) {
				cut(e[mxnow-n].u, mxnow); cut(e[mxnow-n].v, mxnow);
			} else lk = 0;
		} 
		if (lk) {
			link(e[i].u, i + n); link(e[i].v, i + n);
		}
		if (findroot(1) == findroot(n)) {
			split(1, n);
			ans = min(ans, e[i].a + val[mx[n]]);
		}
	}
	printf("%d\n", ans == 0x7fffffff ? -1 : ans);
	return 0;
} 
posted @ 2020-04-26 20:10  AK_DREAM  阅读(155)  评论(0编辑  收藏  举报