LCT例题·BZOJ2049洞穴勘测、BZOJ3669魔法森林
LCT是个好东西。
BZOJ2049洞穴勘测
原题地址:http://www.lydsy.com/JudgeOnline/problem.php?id=2049
题意:
n个点m个操作,操作有三种
1.Connect 连接两个点;
2.Destroy 删除两个点之间的连接;
3.Query 查询两个点是否连通;
题解:
LCT入门,只有几个基础操作
%黄学长
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=10005;
struct node
{
int fa,ch[2];
bool rez;
};
node tr[N];
int n,m;
struct Link_Cut_Tree
{
void init(int x)
{
tr[x].fa=tr[x].ch[0]=tr[x].ch[0]=0; tr[x].rez=0;
}
bool isroot(int x) //是否是该条链的splay的root
{
return (tr[tr[x].fa].ch[0]!=x)&&(tr[tr[x].fa].ch[01]!=x);
}
void pushdown(int x)
{
if(tr[x].rez)
{
swap(tr[x].ch[0],tr[x].ch[1]);
tr[x].rez^=1; tr[tr[x].ch[0]].rez^=1; tr[tr[x].ch[1]].rez^=1;
return;
}
}
void push(int x)
{
if(!isroot(x)) push(tr[x].fa);
pushdown(x);
}
void rotate(int x)
{
int y=tr[x].fa; int z=tr[y].fa;
int l,r;
if(tr[y].ch[0]==x) l=0; else l=1; r=l^1;
if(!isroot(y))
{
if(tr[z].ch[0]==y) tr[z].ch[0]=x; else tr[z].ch[1]=x;
}
tr[y].ch[l]=tr[x].ch[r]; tr[x].ch[r]=y;
tr[x].fa=z; tr[y].fa=x; tr[tr[y].ch[l]].fa=y;
// update(y); update(x);
}
void splay(int x)
{
push(x);
while(!isroot(x))
{
int y=tr[x].fa; int z=tr[y].fa;
if(!isroot(y))
{
if((tr[z].ch[0]==y)^(tr[y].ch[1]==x)) rotate(y);zag
else rotate(x);/zig
}
rotate(x);
}
}
void access(int x)
{
int y=0;
for(;x;y=x,x=tr[x].fa)
{
splay(x);
tr[x].ch[1]=y;//y的深度>x,y成为x的右儿子
//update(x);
}
}
int findroot(int x)
{
access(x); splay(x);
while(tr[x].ch[0]) x=tr[x].ch[0];
return x;
}
void rever(int x)
{
access(x); splay(x); tr[x].rez^=1;
}
void link(int x,int y)
{
if(findroot(x)==findroot(y)) {puts("-1");return;}
rever(x); tr[x].fa=y;
}
void cut(int x,int y)
{
if(findroot(x)!=findroot(y)) {puts("-1");return;}
rever(x); access(y); splay(y); //先把X搞成根了 所以深度较小
tr[y].ch[0]=tr[x].fa=0; //update(y);
}
void query(int x,int y)
{
if (findroot(x)==findroot(y)) printf("Yes\n");
else printf("No\n");
}
}LCT;
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<=n;i++)
{
LCT.init(i);
}
while(m--)
{
char opt[10]; int u,v;
scanf("%s",opt); scanf("%d%d",&u,&v);
if(opt[0]=='C')
{
LCT.link(u,v);
}
else if(opt[0]=='D')
{
LCT.cut(u,v);
}
else
LCT.query(u,v);
}
return 0;
}
BZOJ3669魔法森林
原题地址:http://www.lydsy.com/JudgeOnline/problem.php?id=2049
题意:给定一个图,每条边有ai、bi两种路径,选择一条1~n的路径,使得这条路径上max(ai)+max(bi)最小。
题解:这道题非常妙的一点在于把边看做一个点,连接着这条边的左右端点。
其次,先把ai排序,按照类似与做最小生成树的方法依次添加,若左右端点未联通,则直接link(端点u,代表边的节点)、link(端点v,代表边的节点)。若联通,则判断当前u-v路径上的最大bi值是否大于当前bi,若是,断原边,连现边。当达到1~n连通后,每次比较当前ans与记录的最小ans即可。
之前为什么更新时是直接比较b值有些不理解,其实是这样:
考虑直接用比较b值的正确性:
1、 如果加入这条边前1,N不连通,则后面一定会加入边,由于a值递增,后面的a值比现在两条边的a 值都要大,因此当前边的a值大小不会影响。
2、如果加入这条边前1,N已联通,则之前的最小答案已经记录,不会对最终答案进行影响。
ps.这里学长对ai<=30,bi<=50000的子问题给了个思路,固定一个量,枚举ai,对满足条件的边的bi构造最小生成树。
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=50005;
const int M=100005;
int val[M+N];
const int inf=0x7fffffff;
int ans;
struct eadge
{
int u,v,a,b;
}e[M];
bool cmp(const eadge &A,const eadge &B)
{
return A.a<B.a;
}
struct node
{
int ch[2],fa;
int pos,maxn;
bool rev;
};
node tr[M+N];
struct Link_Cut_Tree
{
void init(int x)
{
tr[x].fa=tr[x].ch[0]=tr[x].ch[1]=tr[x].pos=tr[x].maxn=tr[x].rev=0;
}
void pushdown(int x)
{
if(!x) return;
if(tr[x].rev)
{
swap(tr[x].ch[0],tr[x].ch[1]);
tr[tr[x].ch[0]].rev^=1;
tr[tr[x].ch[1]].rev^=1;
tr[x].rev^=1;
}
}
void push(int x)
{
if(!isroot(x)) push(tr[x].fa);
pushdown(x);
}
void update(int x)
{
if(!x) return;
tr[x].pos=x; tr[x].maxn=val[x]; //因为其左右子树都有可能更改,因此不能根据ch[0/1]更改,而是从自己val开始
if(tr[x].ch[0])
if(val[tr[x].pos]<val[tr[tr[x].ch[0]].pos]) {tr[x].maxn=tr[tr[x].ch[0]].maxn; tr[x].pos=tr[tr[x].ch[0]].pos;}
if(tr[x].ch[1])
if(val[tr[x].pos]<val[tr[tr[x].ch[1]].pos]) {tr[x].maxn=tr[tr[x].ch[1]].maxn; tr[x].pos=tr[tr[x].ch[1]].pos;}
}
bool isroot(int x)
{
return (tr[tr[x].fa].ch[0]!=x)&&(tr[tr[x].fa].ch[1]!=x);
}
void rotate(int x)
{
int y=tr[x].fa; int z=tr[y].fa;
int l,r;
if(tr[y].ch[0]==x) l=0; else l=1; r=l^1;
if(!isroot(y))
{
if(tr[z].ch[0]==y) tr[z].ch[0]=x;
else tr[z].ch[1]=x;
}
tr[y].ch[l]=tr[x].ch[r]; tr[tr[x].ch[r]].fa=y;
tr[x].ch[r]=y; tr[y].fa=x; tr[x].fa=z;
update(y); update(x);
}
void splay(int x)
{
push(x);
while(!isroot(x))
{
int y=tr[x].fa; int z=tr[y].fa;
if(!isroot(y))
{
if((tr[z].ch[0]==y)^(tr[y].ch[0]==x)) rotate(y);
else rotate(x);
}
rotate(x);
}
}
void access(int x)
{
int y=0;
for(;x;y=x,x=tr[x].fa)
{
splay(x);
tr[x].ch[1]=y;
update(x);
}
}
int findroot(int x)
{
access(x); splay(x);
while(tr[x].ch[0]!=0) x=tr[x].ch[0];
return x;
}
void rever(int x)
{
access(x);splay(x); tr[x].rev^=1;
}
void link(int x,int y)
{
rever(x); tr[x].fa=y;
}
void cut(int x,int y)
{
if(findroot(x)!=findroot(y)) return;
rever(x); access(y); splay(y);
tr[x].fa=tr[y].ch[0]=0;
update(y);
}
int query(int x,int y)
{
rever(x); access(y); splay(y);
return tr[y].pos;
}
}LCT;
int main()
{
int n,m;
scanf("%d%d",&n,&m);
memset(val,0,sizeof(val));
for(int i=1;i<=m;i++)
{
scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].a,&e[i].b);
}
sort(e+1,e+m+1,cmp);
for(int i=1;i<=m;i++)
{
val[n+i]=e[i].b;
}
for(int i=0;i<=n+m;i++)
{
LCT.init(i);
}
ans=inf;
for(int i=1;i<=m;i++)
{
int fx=LCT.findroot(e[i].u); int fy=LCT.findroot(e[i].v);
if(fx!=fy)
{
LCT.link(e[i].u,i+n);
LCT.link(i+n,e[i].v);
}
else
{
int k=LCT.query(e[i].u,e[i].v);
if(val[k]>e[i].b)
{
LCT.cut(e[i].u,k);
LCT.cut(e[i].v,k);
LCT.link(e[i].u,i+n);
LCT.link(e[i].v,i+n);
}
}
if(LCT.findroot(1)==LCT.findroot(n)) ans=min(ans,val[LCT.query(1,n)]+e[i].a);
}
if(ans!=inf)
printf("%d",ans);
else printf("-1");
return 0;
}
最后安利dalao的浅谈LCT:
http://blog.csdn.net/NOIAu/article/details/75451616
写得真心好。