选择 (choice)
题目来源
某集训试题
题面
题目描述
现在我想知道自己是否还有选择。
给定n个点m条边的无向图以及顺序发生的q个事件。
每个事件属于下面两种之一:
1.删除某一条图上仍存在的边;
2.询问是否存在两条边不相交的路径可以从点u出发到点v.
输入
第一行三个整数n,m,q
接下来m行,每行两个整数u,v,表示u和v之间有一条边
接下来q行,每行一个大写字母o和2个整数u,v,依次表示按顺序发生的q个事件:
当o为’Z’时,表示删除一条u和v之间的边
当o为’P’时,表示询问是否存在两条边不相交的路径可以从点u出发到点v
输出:
对于每组询问,如果存在,输出Yes,否则输出No (每组询问的回答用换行符隔开).
数据范围:
对于20%的数据,max(n,m,p)≤100
对于100%的数据,max(n,m,p)≤100000
实测发现,数据保证每个事件中 u!=v.
限制
时间限制: 1s
空间限制: 256M
题解
首先,这个没有强制在线,我们可以把删边转化为倒着加边。
问题在于如何维护.
官方题解给的是用并查集维护。
几个大佬分别采用了:LCA+并查集,并查集+乱搞,并查集+tarjan(边双联通分量)的做法,都有一定的道理,而且效率很高。
不过作为一个蒟蒻,我想到的做法是LCT. 虽然可以A,但是常数巨大,极限数据用时是标解的5倍左右,是时限的0.6倍左右.
怎么做呢?
原图上的点在LCT上的初始权值均设为1.
每当加入u->v边的时候,分两类情况讨论:
u,v尚未联通.此时利用并查集维护联通性(LCT亦可,常数较大)。将u,v在并查集中连通。设立新节点node. 之后在LCT中,link(u,node),link(node,v). 新节点的初始权值为0.这实际上相当于加入一条权值为0的边.
u,v已经联通.此时将LCT中u,v链上的所有点权值改为1.
每当询问u,v的时候,我们判断一下:
如果u,v不联通,输出0.
如果u,v联通,在LCT中查询u,v链上所有点的最小权值.若最小值为0,则输出no;若最小值为1,则输出yes.
这么做的正确性基于几个基本事实:
- 如果u,v之间有两条边不相交的路径,那么u,v路径上任意两点之间都有两条边不相交的路径.
- 如果 i 到 j 满足题意, j 到 k 满足题意,那么i到k也是满足题意的.
- LCT中权值为0的边 (其实是权值为0的虚拟节点) 即是求双联通分量的时候遇到的桥边.
- 连在已联通的树链上的边实际上就是tarjan中的返祖边.
于是就相当于是用LCT来干了一件本来应该用并查集和tarjan完成的事。
这个题读数有一些诡异,读入的是删除的边的两个端点。我开了两个堆来干这件事.先将所有边压入堆1中,将要删的边压入堆2中,然后就像在堆中删除元素那样搞就行了。
Code
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define MAXN 255000
void _r(int& x)
{
char c=getchar();
while(c<'0'||c>'9')
{
c=getchar();
}
for(x=0;c>='0'&&c<='9';c=getchar())
{
x=(x<<1)+(x<<3)+c-'0';
}
return ;
}
struct node
{
int x,y,c;
node(int a=0,int b=0,int cc=0)
{
x=a;
y=b;
c=cc;
}
bool operator < (const node& p) const
{
return (x==p.x)?(y<p.y):(x<p.x);
}
bool operator == (const node& p) const
{
return x==p.x&&y==p.y;
}
}E[MAXN];
int Ans[MAXN],tot;
priority_queue<node>P1;
priority_queue<node>P2;
int n,m,Q;
char op[10];
int ch[MAXN][2],tag[MAXN],rev[MAXN],val[MAXN],mn[MAXN],fa[MAXN];
int st[MAXN],top;
bool notroot(int p)
{
return ch[fa[p]][0]==p||ch[fa[p]][1]==p;
}
void cal(int p)
{
int ls=ch[p][0],rs=ch[p][1];
int mm=val[p];
if(ls) //注意判一下,不然mm可能会意外地变成0;
{
mm=min(mm,mn[ls]);
}
if(rs)
{
mm=min(mm,mn[rs]);
}
mn[p]=mm;
return ;
}
inline void zg(int x,int d)
{
int y=fa[x],z=fa[y];
if(notroot(y))
{
if(ch[z][0]==y)
{
ch[z][0]=x;
}
else
{
ch[z][1]=x;
}
}
fa[x]=z;
ch[y][d]=ch[x][!d];
fa[ch[y][d]]=y;
ch[x][!d]=y;
fa[y]=x;
cal(y);
cal(x);
return ;
}
inline void push(int p)
{
int ls=ch[p][0],rs=ch[p][1];
if(tag[p])
{
tag[p]=0;
tag[ls]=tag[rs]=1;
val[ls]=val[rs]=mn[ls]=mn[rs]=1;
} //标记下放顺序无所谓;
if(rev[p])
{
rev[p]=0;
rev[ls]^=1;
rev[rs]^=1;
swap(ch[p][0],ch[p][1]);
}
return ;
}
inline void splay(int x)
{
int y,z;
st[++top]=x;
for(int i=x;notroot(i);i=fa[i])
{
st[++top]=fa[i]; //之前把fa[i]敲成了fa[x],调了很久才发现是这个问题
}
for(;top;--top)
{
push(st[top]);
}
while(notroot(x))
{
y=fa[x];
z=fa[y];
if(notroot(y))
{
if(ch[z][0]==y^ch[y][0]==x)
{
zg(x,ch[fa[x]][1]==x);
}
else
{
zg(y,ch[fa[y]][1]==y);
}
}
zg(x,ch[fa[x]][1]==x);
}
return ;
}
inline void access(int x)
{
for(int t=0;x;t=x,x=fa[x])
{
splay(x);
ch[x][1]=t;
cal(x);
}
return ;
}
inline void makeroot(int x)
{
access(x);
splay(x);
rev[x]^=1;
return ;
}
void link(int x,int y)
{
makeroot(x);
fa[x]=y;
return ;
}
void change(int x,int y)
{
makeroot(x);
access(y);
splay(y);
tag[y]=1;
val[y]=mn[y]=1; //这里可以不动mn[y];
return ;
}
int F[MAXN],N;
int getfa(int x)
{
return F[x]==x?x:F[x]=getfa(F[x]);
}
inline int query(int x,int y)
{
if(getfa(x)!=getfa(y))
{
return 0;
}
makeroot(x);
access(y);
splay(y);
return mn[y];
}
inline void Link(int x,int y)
{
int fx,fy;
fx=getfa(x);
fy=getfa(y);
if(fx!=fy)
{
++N;//新建节点,初值默认为0;
link(x,N);
link(N,y);
F[fx]=fy;
}
else
{
change(x,y);
}
return ;
}
int main()
{
//freopen("choice.in","r",stdin);
//freopen("choice.out","w",stdout);
_r(n);
_r(m);
_r(Q);
for(int i=1,x,y;i<=m;i++)
{
_r(x);
_r(y);
if(x>y)
{
swap(x,y);
}
P1.push(node(x,y));
}
for(int i=1,x,y;i<=Q;i++)
{
scanf("%s",op);
_r(x);
_r(y);
if(x>y)
{
swap(x,y);
}
if(op[0]=='Z')
{
P2.push(node(x,y));
E[i]=node(x,y,0);
}
else
{
E[i]=node(x,y,1);
}
}
node tmp;
for(int i=1;i<=n;i++)
{
F[i]=i;
val[i]=1;
}
N=n;
while(P1.size())
{
while(P2.size()&&(P2.top()==P1.top()))
{
P2.pop();
P1.pop();
}
if(P1.size())//这里一定要判!
{
tmp=P1.top();
P1.pop(); //先前忘写这句,直接跑死
Link(tmp.x,tmp.y);
}
}
for(int i=Q;i>=1;i--)
{
tmp=E[i];
if(tmp.c==1)
{
Ans[i]=query(tmp.x,tmp.y);
} //询问
else
{
Link(tmp.x,tmp.y);
Ans[i]=-1;
} //加边
}
for(int i=1;i<=Q;i++)
{
if(Ans[i]>=0)
{
if(Ans[i]==0)
{
printf("No\n");
}
else
{
printf("Yes\n");
}
}
}
return 0;
}