3669: [Noi2014]魔法森林
Time Limit: 30 Sec
Memory Limit: 512 MB
Submit: 1731
Solved: 1047
[ Submit][ Status][ Discuss]
Description
为了得到书法大家的真传,小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型守护精灵的个数之和。
Input
第1行包含两个整数N,M,表示无向图共有N个节点,M条边。 接下来M行,第行包含4个正整数Xi,Yi,Ai,Bi,描述第i条无向边。其中Xi与Yi为该边两个端点的标号,Ai与Bi的含义如题所述。 注意数据中可能包含重边与自环。
Output
输出一行一个整数:如果小E可以成功拜访到隐士,输出小E最少需要携带的守护精灵的总个数;如果无论如何小E都无法拜访到隐士,输出“-1”(不含引号)。
Sample Input
【输入样例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
Sample Output
【输出样例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。
HINT
2<=n<=50,000
0<=m<=100,000
1<=ai ,bi<=50,000
Source
题解:并查集+LCT
先说一下这个题中学到的一个不错的思想:化边权为点权,之前写树链剖分的时候曾经把边权下放为点权,然后进行处理,但是这种方式用在这里显然不合适,因为LCT 动态树会旋转换根,所有无法下放,那么怎么做呢?因为不管怎么改变树的形态,两个点确定一条边的关系是不变的,所有我们可以把边缩成一点,然后把边权给这个点的点权,然后从缩成的这个点向这条边的两个端点连边,这样就可以解决边权的问题。
因为这个题要维护两个边权,并且要求最后的答案使路径上两个最大边权的和最小,直接两个一块处理,很难搞。所有我们把一个边权按从小到大的顺序排序,然后每次往树中加边,有点类似最小生成树的方式。但是加边时必须保证不能出现环,如果出现环怎么办呢?我们可以在lct中维护每个路径中最大值所在的位置,如果加入该边出现环的话,就判断一下这条路径上的最大值是否大于当前边的权值,如果大于,就拆边建立新边。LCT中只是维护了一个权值,那么我们要求两个权值的和最小,那么如何更新答案能?其实很简单,判断一下起点和终点是否连通,如果连通就用当前边的未加入LCT的权值和LCT中维护的1到N的最大值来更新答案,因为是升序排列,所有可以保证该边未加入LCT的权值一定是最大的。那么肯定有疑问,会不会1到N的路径上根本就没有新加入的那条边呢,那么这样这条路径上一个权值的最大值不就多算了吗?首先这种情况会发生,但是并不会影响最终的答案。因为如果路径上没有这条边,那么之前1到N就是连通的,那么之前在加入1到N中某条路径的时候一定已经计算过正确的答案了,因为当前边的未加入LCT的权值和LCT中维护的1到N的最大值一定比那个答案要大,所有不会有影响。
总之,思路清晰+不手残,就可以AC了。
说一下自己遇到的一个问题,就是在splay时,如果每次都用memset给st数组清零的话,会TLE,慢到你不能想象
如果不需要拆边而两个点又在一个集合中的话,后面的连边操作就跳过。
#include
#include
#include
#include
#define N 300003
using namespace std;
int n,m;
int fa[N],ch[N][2],maxn[N],key[N],rev[N],size[N];
int mp[N],top,st[N];
struct data
{
int a,b,x,y;
};data tr[N];
int cmp(data a,data b)
{
return a.akey[size[x]]) size[x]=size[l];
if (key[size[r]]>key[size[x]]) size[x]=size[r];
}
void rotate(int x)
{
int y=fa[x]; int z=fa[y]; int which=get(x);
if (!isroot(y))
ch[z][ch[z][1]==y]=x;
fa[x]=z; ch[y][which]=ch[x][which^1]; fa[ch[y][which]]=y;
ch[x][which^1]=y; fa[y]=x;
update(y); update(x);
}
void splay(int x)
{
top=0;
st[++top]=x;
for (int i=x;!isroot(i);i=fa[i])
st[++top]=fa[i];
for (int i=top;i>0;i--)
pushdown(st[i]);
while (!isroot(x))
{
int y=fa[x];
if (!isroot(y))
rotate(get(x)==get(y)?y:x);
rotate(x);
}
}
void access(int x)
{
int t=0;
while (x)
{
splay(x);
ch[x][1]=t; update(x);
t=x; x=fa[x];
}
}
void rever(int x)
{
access(x); splay(x); rev[x]^=1;
}
void link(int x,int y)
{
rever(x); fa[x]=y; splay(x);
}
void cut(int x,int y)
{
rever(x); access(y); splay(y);
fa[x]=ch[y][0]=0; update(y);
}
int find(int x)
{
if (mp[x]==x) return x;
mp[x]=find(mp[x]);
return mp[x];
}
int seek(int x,int y)
{
rever(x); access(y); splay(y);
return size[y];
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++)
scanf("%d%d%d%d",&tr[i].x,&tr[i].y,&tr[i].a,&tr[i].b);
sort(tr+1,tr+m+1,cmp);
for (int i=1;i<=n;i++)
mp[i]=i;
int ans=1000000000;
for (int i=1;i<=m;i++)
{
int k=find(tr[i].x);
int t=find(tr[i].y);
if (k!=t)
{
mp[t]=k;
}
else
{
int l=seek(tr[i].x,tr[i].y);
if (key[l]>tr[i].b)
{
cut(l,tr[l-n].x);
cut(l,tr[l-n].y);
}
else
{
if (find(1)==find(n))
ans=min(ans,key[seek(1,n)]+tr[i].a);
continue;
}
}
key[i+n]=tr[i].b; size[i+n]=i+n;
link(tr[i].x,n+i);
link(tr[i].y,n+i);
if (find(1)==find(n))
{
int l=seek(1,n); ans=min(key[l]+tr[i].a,ans);
}
}
if (ans==1000000000)
printf("-1\n");
else
printf("%d\n",ans);
return 0;
}