Solution[NOI204]魔法森林
题目大意:给定一个无向图,每条边有两个权值\(A\),\(B\),求一条从\(1\)到\(n\)的路径,使得\(max\{A\} + max\{B\}\)最小
分析:有两个限制不太好做,我们考虑枚举\(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;
}