【NOI2014】魔法森林
为了得到书法大家的真传,小 E E E同学下定决心去拜访住在魔法森林中的隐士。魔法森林可以被看成一个包含个 N N N节点 M M M条边的无向图,节点标号为 1 … n 1…n 1…n,边标号为 1 … m 1…m 1…m。初始时小E同学在 11 号节点,隐士则住在 n n n 号节点。小E需要通过这一片魔法森林,才能够拜访到隐士。
魔法森林中居住了一些妖怪。每当有人经过一条边的时候,这条边上的妖怪就会对其发起攻击。幸运的是,在 1 1 1 号节点住着两种守护精灵: A A A型守护精灵与 B B B型守护精灵。小 E E E可以借助它们的力量,达到自己的目的。
只要小 E E E带上足够多的守护精灵,妖怪们就不会发起攻击了。具体来说,无向图中的每一条边 e i e_i ei 包含两个权值 a i a_i ai 与 b i b_i bi。若身上携带的A型守护精灵个数不少于 a i a_i ai,且B型守护精灵个数不少于 b i b_i bi,这条边上的妖怪就不会对通过这条边的人发起攻击。当且仅当通过这片魔法森林的过程中没有任意一条边的妖怪向小 E E E发起攻击,他才能成功找到隐士。
由于携带守护精灵是一件非常麻烦的事,小 E E E想要知道,要能够成功拜访到隐士,最少需要携带守护精灵的总个数。守护精灵的总个数为 A A A型守护精灵的个数与 B B B型守护精灵的个数之和。
输入格式
第 1 1 1行包含两个整数 n , m n,m n,m,表示无向图共有 n n n 个节点, m m m 条边。
接下来 m m m 行,第 i + 1 i+1 i+1 行包含 4 4 4个正整数 x i , y i , a i , b i x_i,y_i,a_i,b_i xi,yi,ai,bi,描述第 i i i 条无向边。其中 x i x_i xi 与 y i y_i yi 为该边两个端点的标号, a i a_i ai 与 b i b_i bi的含义如题所述。
注意数据中可能包含重边与自环。
输出格式
输出一行一个整数:如果小 E E E可以成功拜访到隐士,输出小 E E E最少需要携带的守护精灵的总个数;如果无论如何小 E E E都无法拜访到隐士,输出 “ − 1 ” “-1” “−1”(不含引号)。
样例一
input
4 5
1 2 19 1
2 3 8 12
2 4 12 15
1 3 17 8
3 4 1 17
output
32
explanation
如果小E走路径1→2→4,需要携带 19+15=3419+15=34 个守护精灵;
如果小E走路径1→3→4,需要携带 17+17=3417+17=34 个守护精灵;
如果小E走路径1→2→3→4,需要携带 19+17=3619+17=36 个守护精灵;
如果小E走路径1→3→2→4,需要携带 17+15=3217+15=32 个守护精灵。
综上所述,小E最少需要携带 3232 个守护精灵。
样例二
input
3 1
1 2 1 1
output
-1
explanation
小 E E E无法从 1 1 1号节点到达 3 3 3号节点,故输出 − 1 -1 −1。
题意
- 就是给你一张无向图,每一条边都有两个属性 a i a_i ai和 b i b_i bi,你拥有两个属性值 A A A和 B B B,能通过一条边当且仅当 A > a i A>a_i A>ai且 B > b i B>b_i B>bi,通过后 A A A和 B B B不变,求满足能从 1 1 1走到 n n n的最小 A + B A+B A+B
题解
- 考虑按属性 A A A对所有边排序,首先容易知道的结论是:如果最优路径经过的边的所有属性 A A A为集合 A 1 , A 2 . . . . A k A_1,A_2....A_k A1,A2....Ak,属性 B B B对应集合为 B 1 , B 2 . . . . B k B_1,B_2....B_k B1,B2....Bk,那么答案就是 m a x i = 1 k A i + m a x i = 1 k B i max_{i=1}^{k}{A_i}+max_{i=1}^k{B_i} maxi=1kAi+maxi=1kBi,所以就可以考虑枚举 A A A的最大值,然后去找最小的且使得 1 1 1与 n n n连通的最大 B B B值,那么先将所有边根据属性 A A A排序,然后用 L C T LCT LCT去维护属性 B B B的最小生成树,至于为什么是最小生成树呢?利用 K r u s k a l Kruskal Kruskal算法的思想可以知道这样求出的最大边权最小。
- 这里小技巧是将边权转化为点权的方法:两点之间再新建一个节点,节点权值设为边权
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
#define inf 0x3f3f3f3f
namespace LCT{
int ch[maxn][2],fa[maxn],sta[maxn],mark[maxn];
int val[maxn],ans[maxn];
inline bool not_root(int x) {
return ch[fa[x]][0]==x||ch[fa[x]][1]==x;
}
inline int dir(int x) {
return ch[fa[x]][1]==x;
}
inline void add_mark(int x) { //将x这颗子树翻转
swap(ch[x][0],ch[x][1]);
mark[x]^=1;
}
inline void push_down(int x) {
if(mark[x]) {
if(ch[x][0]) add_mark(ch[x][0]);
if(ch[x][1]) add_mark(ch[x][1]);
mark[x]=0;
}
}
inline void push_up(int x) {
ans[x]=x;
if(val[ans[ch[x][0]]]>val[ans[x]]) ans[x]=ans[ch[x][0]];
if(val[ans[ch[x][1]]]>val[ans[x]]) ans[x]=ans[ch[x][1]];
}
inline void pushall(int x) {
if(not_root(x)) pushall(fa[x]);
push_down(x);
}
inline void rotate(int x){
int y=fa[x],z=fa[y],k=dir(x);
if(ch[x][k^1]) fa[ch[x][k^1]]=y;ch[y][k]=ch[x][k^1];
if(not_root(y)) ch[z][dir(y)]=x;fa[x]=z;
ch[x][k^1]=y;fa[y]=x;
push_up(y);push_up(x);
}
inline void splay(int x,int goal=0) {
pushall(x);
while(not_root(x)) {
int y=fa[x],z=fa[y];
if(not_root(y)) {
if(dir(x)==dir(y)) rotate(y);
else rotate(x);
}
rotate(x);
}
//push_up(x);
}
inline void access(int x) { //从原树的根向x拉一条实链
for(int y=0;x;y=x,x=fa[x]) {
splay(x);ch[x][1]=y;push_up(x);
}
}
inline void make_root(int x) { //使x成为splay的根
access(x);splay(x);add_mark(x);
}
inline int find_root(int x) { //找到x在原树中的根
access(x);splay(x);
while(ch[x][0]) push_down(x),x=ch[x][0];
splay(x);
return x;
}
inline void split(int x,int y) { //拉出一条x->y的实链,y为splay根
make_root(x);access(y);splay(y);
}
inline bool link(int x,int y) { //连接x与y,若已经在同一颗原树中,返回0
make_root(x);
if(find_root(y)==x) return 0;
fa[x]=y;return 1;
}
inline bool cut(int x,int y) {
make_root(x);
if(find_root(y)!=x||fa[y]!=x||ch[y][0]) return 0;
fa[y]=ch[x][1]=0;
push_up(x);
return 1;
}
inline int query(int l,int r) {
split(l,r);
return ans[r];
}
};
using namespace LCT;
struct edge{
int u,v,a,b;
friend bool operator<(const edge &c,const edge &d) {
return c.a<d.a;
}
}o[maxn];
int n,m;
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++) scanf("%d %d %d %d",&o[i].u,&o[i].v,&o[i].a,&o[i].b);
sort(o+1,o+m+1);
int ans=inf;
for(int i=1;i<=m;i++) {
if(find_root(o[i].u)==find_root(o[i].v)){
int u=query(o[i].u,o[i].v);
if(o[i].b<val[u]) {
cut(o[u-n].u,u);
cut(o[u-n].v,u);
}else {
if(find_root(1)==find_root(n)) ans=min(ans,o[i].a+val[query(1,n)]);
continue;
}
}
val[i+n]=o[i].b;
link(o[i].u,i+n);link(o[i].v,i+n);
if(find_root(1)==find_root(n)) ans=min(ans,o[i].a+val[query(1,n)]);
}
if(ans==inf) printf("%d\n",-1);
else printf("%d\n",ans);
}