题目链接

题目大意:给定一个无向图,每条边有两个权值\(A\),\(B\),求一条从\(1\)\(n\)的路径,使得\(max\{A\} + max\{B\}\)最小

\(LCT\),枚举,最小生成树


分析:有两个限制不太好做,我们考虑枚举\(A\)的限制\(A_{limit}\)

每次把\(A \leq A_{limit}\)的边加入图中,然后我们只需要使得找一条路径使得\(max\{B\}\)最小,\(ans = A_{limit} + max\{B\}\)

再套个二分+并查集判连通性吧

以上为口胡算法,复杂度爆炸

然后我们来考虑优化:

首先枚举\(A_{limit}\),因为我们最后还不是限制了边的加入,所以可以直接把边按照\(A\)排序,然后加边

至于维护\(max\{B\}\),我们发现有些边是没有用的,我们只需要根据\(B\)贪心的加边,直到\(1\),\(n\)联通为止

听上去有点难以维护,但其实就是个\(Kruskal\)最小生成树.

然后问题就是动态加边维护\(MST\)

我们发现最多加入\(n-1\)条边后就一定会出现环,我们只需要把这条边加进去,把边权最大的边删掉即可

加边之前没有环是一颗森林,考虑动态树,我们就用\(LCT\)来维护吧主要是我只会这个

朴素\(LCT\)处理点权,我们用一个点来表示边,然后就是\(LCT\)模板了

代码:

#include <algorithm>
#include <cstdio>
#include <cctype>
using namespace std;
const int maxm = 1e5 + 100;
inline int read(){
	int x = 0;char c = getchar();
	while(!isdigit(c))c = getchar();
	while(isdigit(c))x = x * 10 + c - '0',c = getchar();
	return x;
}
namespace LCT{//LCT板子
	#define ls ch[x][0]
	#define rs ch[x][1]
	const int maxn = 2e5;
	int ch[maxn][2],faz[maxn],v[maxn],val_mx[maxn],rev[maxn];//注意:我们不仅要维护最大值,还要维护最大边权的边的编号,v是边权(点权),val_mx就是LCT维护的值
	inline int isroot(int x){return ch[faz[x]][0] != x && ch[faz[x]][1] != x;}
	inline int chk(int x){return ch[faz[x]][1] == x;}
	inline void pushup(int x){//上传信息
		val_mx[x] = x;
		if(ls && v[val_mx[ls]] > v[val_mx[x]])val_mx[x] = val_mx[ls];
		if(rs && v[val_mx[rs]] > v[val_mx[x]])val_mx[x] = val_mx[rs];
	}
	inline void pushr(int x)//一下均为模板{swap(ls,rs);rev[x] ^= 1;}
	inline void pushdown(int x){
		if(!rev[x])return;
		if(ls)pushr(ls);
		if(rs)pushr(rs);
		rev[x] = 0;
	}
	inline void rotate(int x){
		int y = faz[x],z = faz[y],k = chk(x),w = ch[x][!k];
		ch[y][k] = w;if(w)faz[w] = y;
		if(!isroot(y))ch[z][chk(y)] = x;faz[x] = z;
		ch[x][!k] = y;faz[y] = x;
		pushup(y),pushup(x);
	}
	inline void splay(int x){
		static int st[maxn];
		int y = x,top = 0;
		st[++top] = y;
		while(!isroot(y))st[++top] = y = faz[y];
		while(top)pushdown(st[top--]);
		while(!isroot(x)){
			int y = faz[x];
			if(!isroot(y))
				chk(x) == chk(y) ? rotate(y) : rotate(x);
			rotate(x);
		}
	}
	inline void access(int x){
		for(int y = 0;x;y = x,x = faz[x])
			splay(x),ch[x][1] = y,pushup(x);
	}
	inline int findroot(int x){
		access(x),splay(x);
		while(ch[x][0])pushdown(x),x = ch[x][0];
		splay(x);
		return x;
	}
	inline void makeroot(int x){access(x),splay(x),pushr(x);}
	inline void split(int x,int y){makeroot(x),access(y),splay(y);}
	inline void link(int x,int y){makeroot(x);if(findroot(y) != x)faz[x] = y;}
	inline void cut(int x,int y){makeroot(x);if(findroot(y) == x && faz[y] == x && !ch[y][0])faz[y] = ch[x][1] = 0,pushup(x);}
	#undef ls
	#undef rs
}using namespace LCT;
struct Edge{
	int from,to,dista,distb,id;//id为原来编号
	bool operator < (const Edge &rhs)const{
		return (dista == rhs.dista) ? (distb < rhs.distb) : (dista < rhs.dista);
	}
}org[maxm],Edges[maxm];//org为原来边数组,排序后打乱顺序
int n,m,ans = 0x7fffffff;
int main(){
	n = read(),m = read();
	for(int i = 1;i <= m;i++){
		Edges[i].from = read();
		Edges[i].to = read();
		Edges[i].dista = read();
		val_mx[n + i] = n + i;
		v[n + i] = Edges[i].distb = read();
		Edges[i].id = i;
		org[i] = Edges[i];
	}
	sort(Edges + 1,Edges + 1 + m);
	for(int i = 1;i <= m;i++){
		Edge &e = Edges[i];
		if(e.from == e.to)continue;
		if(findroot(e.from) == findroot(e.to)){
			split(e.from,e.to);
			int mx = val_mx[e.to];//得到最大边的编号,其实就是点的编号
			if(e.distb > v[mx])continue;//如果加进去马上又要删掉还不如不加
			cut(mx,org[mx - n].from);
			cut(mx,org[mx - n].to);
		}
		link(n + e.id,e.from),link(n + e.id,e.to);
		if(findroot(1) == findroot(n))ans = min(ans,Edges[i].dista + (split(1,n),v[val_mx[n]]));
	}
	return printf("%d\n",ans == 0x7fffffff ? -1 : ans),0;
}

相关文章: