[BZOJ3669][Noi2014]魔法森林
[BZOJ3669][Noi2014]魔法森林
试题描述
为了得到书法大家的真传,小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
输出示例
3 1 1 2 1 1
数据规模及约定
2<=n<=50,000
0<=m<=100,000
1<=ai ,bi<=50,000
题解
首先按权值 a 排序,然后动态加入这些边,如果形成环了则把环上权值 b 最大的边删去。这样我们就可以保证在所有权值 a 不超过当前的 ai 时 bi 最小了,所以我们每次加完边后都求一次 1 到 n 的路径上的答案即可。
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <cctype> #include <algorithm> using namespace std; int read() { int x = 0, f = 1; char c = getchar(); while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); } while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); } return x * f; } #define maxn 50010 #define maxm 100010 #define maxnode 150010 #define oo 2147483647 struct Node { int a, b, mxa, mxb; bool rev; Node(): rev(0) {} Node(int _, int __): a(_), b(__) {} } ns[maxnode]; int fa[maxnode], ch[maxnode][2], S[maxnode], top; bool isrt(int u) { return !fa[u] || (ch[fa[u]][0] != u && ch[fa[u]][1] != u); } void maintain(int o) { ns[o].mxa = ns[o].mxb = o; for(int i = 0; i < 2; i++) if(ch[o][i]) { int son = ns[ch[o][i]].mxa, &mea = ns[o].mxa; if(ns[son].a > ns[mea].a) mea = son; son = ns[ch[o][i]].mxb; int &meb = ns[o].mxb; if(ns[son].b > ns[meb].b) meb = son; } return ; } void pushdown(int o) { if(!ns[o].rev) return ; swap(ch[o][0], ch[o][1]); for(int i = 0; i < 2; i++) if(ch[o][i]) ns[ch[o][i]].rev ^= 1; ns[o].rev = 0; return ; } void rotate(int u) { int y = fa[u], z = fa[y], l = 0, r = 1; if(!isrt(y)) ch[z][ch[z][1]==y] = u; if(ch[y][1] == u) swap(l, r); fa[u] = z; fa[y] = u; fa[ch[u][r]] = y; ch[y][l] = ch[u][r]; ch[u][r] = y; maintain(y); maintain(u); return ; } void splay(int u) { int t = u; S[top = 1] = t; while(!isrt(t)) S[++top] = fa[t], t = fa[t]; while(top) pushdown(S[top--]); while(!isrt(u)) { int y = fa[u], z = fa[y]; if(!isrt(y)) { if(ch[y][0] == u ^ ch[z][0] == y) rotate(u); else rotate(y); } rotate(u); } return ; } void access(int u) { splay(u); ch[u][1] = 0; maintain(u); while(fa[u]) splay(fa[u]), ch[fa[u]][1] = u, maintain(fa[u]), splay(u); return ; } void makeroot(int u) { access(u); ns[u].rev ^= 1; return ; } void link(int a, int b) { makeroot(b); fa[b] = a; return ; } void cut(int a, int b) { makeroot(a); access(b); ch[b][0] = fa[a] = 0; maintain(a); return ; } int _a, _b; void query(int a, int b) { makeroot(a); access(b); _a = ns[b].mxa; _b = ns[b].mxb; return ; } struct Edge { int u, v, a, b; Edge() {} Edge(int _1, int _2, int _3, int _4): u(_1), v(_2), a(_3), b(_4) {} bool operator < (const Edge& t) const { return a < t.a; } } es[maxm]; int pa[maxn]; int findset(int x) { return x == pa[x] ? x : pa[x] = findset(pa[x]); } int ans; int main() { int n = read(), m = read(); for(int i = 1; i <= n; i++) ns[i] = Node(0, 0), maintain(i), pa[i] = i; for(int i = 1; i <= m; i++) { int u = read(), v = read(), a = read(), b = read(); es[i] = Edge(u, v, a, b); } sort(es + 1, es + m + 1); ans = oo; // for(int i = 1; i <= m; i++) printf("Edge %d: %d %d %d %d\n", i, es[i].u, es[i].v, es[i].a, es[i].b); for(int i = 1; i <= m; i++) { int u = es[i].u, v = es[i].v, U = findset(u), V = findset(v); ns[i+n] = Node(es[i].a, es[i].b); maintain(i + n); if(U == V) { query(u, v); if(ns[_b].b > es[i].b) { cut(es[_b-n].u, _b); cut(_b, es[_b-n].v); link(u, i + n); link(i + n, v); } } else { pa[V] = U; link(u, i + n); link(i + n, v); } if(findset(1) == findset(n)) { query(1, n); ans = min(ans, ns[_a].a + ns[_b].b); } } printf("%d\n", ans < oo ? ans : -1); return 0; }