题目描述
为了得到书法大家的真传,小E同学下定决心去拜访住在魔法森林中的隐士。魔法森林可以被看成一个包含个N节点M条边的无向图,节点标号为1..N,边标号为1..M。初始时小E同学在号节点1,隐士则住在号节点N。小E需要通过这一片魔法森林,才能够拜访到隐士。
魔法森林中居住了一些妖怪。每当有人经过一条边的时候,这条边上的妖怪就会对其发起攻击。幸运的是,在号节点住着两种守护精灵:A型守护精灵与B型守护精灵。小E可以借助它们的力量,达到自己的目的。
只要小E带上足够多的守护精灵,妖怪们就不会发起攻击了。具体来说,无向图中的每一条边Ei包含两个权值Ai与Bi。若身上携带的A型守护精灵个数不少于Ai,且B型守护精灵个数不少于Bi,这条边上的妖怪就不会对通过这条边的人发起攻击。当且仅当通过这片魔法森林的过程中没有任意一条边的妖怪向小E发起攻击,他才能成功找到隐士。
由于携带守护精灵是一件非常麻烦的事,小E想要知道,要能够成功拜访到隐士,最少需要携带守护精灵的总个数。守护精灵的总个数为A型守护精灵的个数与B型守护精灵的个数之和。
输入
第1行包含两个整数N,M,表示无向图共有N个节点,M条边。 接下来M行,第行包含4个正整数Xi,Yi,Ai,Bi,描述第i条无向边。其中Xi与Yi为该边两个端点的标号,Ai与Bi的含义如题所述。 注意数据中可能包含重边与自环。
输出
输出一行一个整数:如果小E可以成功拜访到隐士,输出小E最少需要携带的守护精灵的总个数;如果无论如何小E都无法拜访到隐士,输出“-1”(不含引号)。
样例输入
4 5
1 2 19 1
2 3 8 12
2 4 12 15
1 3 17 8
3 4 1 17
【输入样例2】
3 1
1 2 1 1
样例输出
32
【样例说明1】
如果小E走路径1→2→4,需要携带19+15=34个守护精灵;
如果小E走路径1→3→4,需要携带17+17=34个守护精灵;
如果小E走路径1→2→3→4,需要携带19+17=36个守护精灵;
如果小E走路径1→3→2→4,需要携带17+15=32个守护精灵。
综上所述,小E最少需要携带32个守护精灵。
【输出样例2】
-1
【样例说明2】
小E无法从1号节点到达3号节点,故输出-1。
提示
2<=n<=50,000
0<=m<=100,000
1<=ai ,bi<=50,000
求一个二维的最小生成树(最小生成森林)中的最大边权,一维的很好做直接按边权排序后加边即可。
那么二维的怎么搞?
我们依旧按第一维排序,这样保证了第一维一定是最小生成树,按排序后顺序枚举所有边,当边两端的点不连通就加入这条边,直接用LCT,link一下即可。
当边两端的点联通,那么我们判断当前枚举边的第二维和1到n路径上第二维边权最大值的关系,如果当前枚举的边小就把那条边删除,加上这条边。
splay要维护第二维边权的最大值,但在LCT上维护边信息并不好做,因此我们将新建一个点tmp来代表边,它的点权就是它代表边的边权。
例如x,y之间有一条边,那么在LCT中就把(x,tmp)和(y,tmp)连接起来。
每次枚举一条边之后判断1和n是否联通,只要联通不管当前边是否被选都用当前边第一维边权和1到n路径上第二维边权最大值来更新答案。
这样更新为什么是对的?
假如这条边选了并且在1到n的路径上,那么这么更新是可以的。
假如这条边选了但不在1到n的路径上,那么说明在这条边选之前1与n就是联通的,这次更新的值一定比答案大,不影响。
假如这条边没选,那这次更新的值也一定比当前答案大,一定不会影响答案。
#include<set> #include<map> #include<cmath> #include<queue> #include<stack> #include<vector> #include<cstdio> #include<bitset> #include<cstring> #include<iostream> #include<algorithm> #define ll long long using namespace std; int n,m; int ans; int r[150010]; int v[150010]; int f[150010]; int st[150010]; int s[150010][2]; int num[150010]; struct miku { int x,y; int a,b; }g[1000010]; bool cmp(miku p,miku q) { return p.a<q.a; } int get(int rt) { return rt==s[f[rt]][1]; } int is_root(int rt) { return rt!=s[f[rt]][0]&&rt!=s[f[rt]][1]; } void pushup(int rt) { num[rt]=rt; if(v[num[s[rt][0]]]>v[num[rt]]) { num[rt]=num[s[rt][0]]; } if(v[num[s[rt][1]]]>v[num[rt]]) { num[rt]=num[s[rt][1]]; } } void pushdown(int rt) { if(r[rt]) { r[s[rt][0]]^=1; r[s[rt][1]]^=1; r[rt]^=1; swap(s[rt][0],s[rt][1]); } } void rotate(int rt) { int fa=f[rt]; int anc=f[fa]; int k=get(rt); if(!is_root(fa)) { s[anc][get(fa)]=rt; } s[fa][k]=s[rt][k^1]; f[s[fa][k]]=fa; s[rt][k^1]=fa; f[fa]=rt; f[rt]=anc; pushup(fa); pushup(rt); } void splay(int rt) { int top=0; st[++top]=rt; for(int i=rt;!is_root(i);i=f[i]) { st[++top]=f[i]; } for(int i=top;i>=1;i--) { pushdown(st[i]); } for(int fa;!is_root(rt);rotate(rt)) { if(!is_root(fa=f[rt])) { rotate(get(fa)==get(rt)?fa:rt); } } } void access(int rt) { for(int x=0;rt;x=rt,rt=f[rt]) { splay(rt); s[rt][1]=x; pushup(rt); } } void reverse(int rt) { access(rt); splay(rt); r[rt]^=1; } int find(int rt) { access(rt); splay(rt); while(s[rt][0]) { rt=s[rt][0]; } return rt; } void link(int x,int y) { reverse(x); f[x]=y; } void cut(int x,int y) { reverse(x); access(y); splay(y); f[x]=0; s[y][0]=0; pushup(y); } int main() { ans=1<<30; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { num[i]=i; } for(int i=1;i<=m;i++) { scanf("%d%d%d%d",&g[i].x,&g[i].y,&g[i].a,&g[i].b); } sort(g+1,g+1+m,cmp); for(int i=1;i<=m;i++) { int x=g[i].x; int y=g[i].y; if(find(x)!=find(y)) { v[n+i]=g[i].b; num[n+i]=n+i; link(x,n+i); link(y,n+i); } else { reverse(x); access(y); splay(y); int val=num[y]; if(v[val]>g[i].b) { cut(val,g[val-n].x); cut(val,g[val-n].y); num[n+i]=n+i; v[n+i]=g[i].b; link(n+i,x); link(n+i,y); } } if(find(1)==find(n)) { reverse(1); access(n); splay(n); ans=min(ans,g[i].a+v[num[n]]); } } if(ans==(1<<30)) { printf("-1"); return 0; } printf("%d",ans); }