【刷题】UOJ #207 共价大爷游长沙
火车司机出秦川,跳蚤国王下江南,共价大爷游长沙。每个周末,勤劳的共价大爷都会开车游历长沙市。
长沙市的交通线路可以抽象成为一个 \(n\) 个点 \(n−1\) 条边的无向图,点编号为 \(1\) 到 \(n\),任意两点间均存在恰好一条路径,显然两个点之间最多也只会有一条边相连。有一个包含一些点对 \((x,y)\) 的可重集合S,共价大爷的旅行路线是这样确定的:每次他会选择 \(S\) 中的某一对点 \((x,y)\),并从 \(x\) 出发沿着唯一路径到达 \(y\) 。
小L是共价大爷的脑残粉,为了见到共价大爷的尊容,小L决定守在这张图的某条边上等待共价大爷的到来。为了保证一定能见到他,显然小L必须选择共价大爷一定会经过的边——也就是所有共价大爷可能选择的路径都经过的边。
现在小L想知道,如果他守在某一条边,是否一定能见到共价大爷。
然而长沙市总是不断的施工,也就是说,可能某个时刻某条边会断开,同时这个时刻一定也有某条新边会出现,且任意时刻图都满足任意两点间均存在恰好一条路径的条件。 注意断开的边有可能和加入的新边连接着相同的两个端点。共价大爷的兴趣也会不断变化,所以S也会不断加入新点对或者删除原有的点对。 当然,小L也有可能在任何时候向你提出守在某一条边是否一定能见到共价大爷的问题。你能回答小L的所有问题吗?
输入格式
输入的第一行包含一个整数 \(id\),表示测试数据编号,如第一组数据的\(id=1\),样例数据的 \(id\) 可以忽略。hack数据中的 \(id\) 必须为 \(0\) 到 \(10\) 之间的整数。hack数据中 \(id\) 的值和数据类型没有任何关系。
输入的第二行包含两个整数 \(n,m\),分别表示图中的点数,以及接下来会发生的事件数,事件的定义下文中会有描述。初始时 \(S\) 为空。
接下来 \(n−1\) 行,每行两个正整数 \(x,y\),表示点 \(x\) 和点 \(y\) 之间有一条无向边。
接下来 \(m\) 行,每行描述一个事件,每行的第一个数 \(type\) 表示事件的类型。
若 \(type=1\),那么接下来有四个正整数 \(x,y,u,v\),表示先删除连接点 \(x\) 和点 \(y\) 的无向边,保证存在这样的无向边,然后加入一条连接点 \(u\) 和点 \(v\) 的无向边,保证操作后的图仍然满足题中所述条件。
若 \(type=2\),那么接下来有两个正整数 \(x,y\),表示在 \(S\) 中加入点对 \((x,y)\) 。
若 \(type=3\),那么接下来有一个正整数 \(x\),表示删除第 \(x\) 个加入 \(S\) 中的点对,即在第 \(x\) 个 \(type=2\) 的事件中加入 \(S\) 中的点对,保证这个点对存在且仍然在 \(S\) 中。
若 \(type=4\),那么接下来有两个正整数 \(x,y\),表示小L询问守在连接点 \(x\) 和点 \(y\) 的边上是否一定能见到共价大爷,保证存在这样的无向边且此时 \(S\) 不为空。
输出格式
对于每个小L的询问,输出“YES”或者“NO”(均不含引号)表示小L一定能或者不一定能见到共价大爷。
样例一
input
0
5 7
1 2
1 3
2 4
1 5
2 1 5
1 1 5 2 5
4 2 5
2 1 4
4 2 5
3 1
4 2 4
output
YES
NO
YES
explanation
最开始将点对 \((1,5)\) 加入到 \(S\) 中,此时点 \(1\) 和点 \(5\) 之间的路径是 \(1→5\)。
接着将连接点 \(1\) 和点 \(5\) 的边断开,加入连接点 \(2\) 和点 \(5\) 的边,我们发现图仍然满足题中所述条件,且点 \(1\) 和点 \(5\) 之间的路径是 \(1→2→5\),经过点了 \(2\) 和点 \(5\) 之间的边,因此第一个询问答案是 YES。
接着将点对 \((1,4)\) 加入到 \(S\) 中,点 \(1\) 和点 \(4\) 之间的路径是 \(1→2→4\),没有经过点 \(2\) 和点 \(5\) 之间的边,因此第二个询问答案是 NO。
接着,我们删除了第一个加入到 \(S\) 中的点对,也就是点对 \((1,5)\),此时 \(S\) 中唯一的点对就是 \((1,4)\),经过了点 \(2\) 和点 \(4\) 之间的边,因此最后一个询问答案是 YES。
样例二
见样例数据下载。
样例三
见样例数据下载。这组数据中 \(type≠1\)。
限制与约定
每组测试数据的限制与约定如下所示:
测试点编号 | $n$ | $m$ | $type=$ | 限制与约定 |
$1$ | $n≤100$ | $m≤100$ | $1,2,3,4$ | |
$2$ | $n≤100000$ | $m≤300000$ | $2,4$ | |
$3$ | ||||
$4$ | $2,3,4$ | |||
$5$ | ||||
$6$ | $1,2,3,4$ | 任意时刻 $|S|≤10$ | ||
$7$ | ||||
$8$ | ||||
$9$ | ||||
$10$ |
时间限制: \(2s\)
空间限制: \(512MB\)
来源
matthew99
题解
http://matthew99.blog.uoj.ac/blog/1771
下载
我的题解
orz 毛爷爷
这题很巧妙地利用了异或的自反性啊
毛爷爷的题解应该说得很清楚了
实际上就是
对于每次加入 \(S\) 点集的点对,把两个端点都异或上一个随机数
那么如果一条边 \((u,v)\) 被所有的点对经过
我们把 \(u\) 变成根,那就一定会有 \(v\) 所在的子树的异或和等于所有点对的异或和,因为每个点对一定会恰好有且仅有一个端点在 \(v\) 所在的子树内嘛
所以用LCT来维护子树信息,每次查询看两个异或和是否相等就可以了
这题目真的巧妙
(如果你用的是srand,然后被hack了,可以试试把你srand的数换成time(0))
#include<bits/stdc++.h>
#define ll long long
#define db double
#define ld long double
const int MAXN=100000+10,MAXM=300000+10;
int n,m,Sval,Sn;
struct edge{
int u,v,w;
};
edge S[MAXM];
#define lc(x) ch[(x)][0]
#define rc(x) ch[(x)][1]
struct LCT{
int ch[MAXN][2],fa[MAXN],rev[MAXN],val[MAXN],sum[MAXN],Isum[MAXN],stack[MAXN],cnt;
inline bool nroot(int x)
{
return lc(fa[x])==x||rc(fa[x])==x;
}
inline void reverse(int x)
{
std::swap(lc(x),rc(x));
rev[x]^=1;
}
inline void pushup(int x)
{
sum[x]=sum[lc(x)]^sum[rc(x)]^Isum[x]^val[x];
}
inline void pushdown(int x)
{
if(rev[x])
{
if(lc(x))reverse(lc(x));
if(rc(x))reverse(rc(x));
rev[x]=0;
}
}
inline void rotate(int x)
{
int f=fa[x],p=fa[f],c=(rc(f)==x);
if(nroot(f))ch[p][rc(p)==f]=x;
fa[ch[f][c]=ch[x][c^1]]=f;
fa[ch[x][c^1]=f]=x;
fa[x]=p;
pushup(f);
pushup(x);
}
inline void splay(int x)
{
cnt=0;
stack[++cnt]=x;
for(register int i=x;nroot(i);i=fa[i])stack[++cnt]=fa[i];
while(cnt)pushdown(stack[cnt--]);
for(register int y=fa[x];nroot(x);rotate(x),y=fa[x])
if(nroot(y))rotate((lc(y)==x)==(lc(fa[y])==y)?y:x);
pushup(x);
}
inline void access(int x)
{
for(register int y=0;x;x=fa[y=x])
{
splay(x);
Isum[x]^=sum[rc(x)];
rc(x)=y;
Isum[x]^=sum[rc(x)];
pushup(x);
}
}
inline void makeroot(int x)
{
access(x);splay(x);reverse(x);
}
inline void split(int x,int y)
{
makeroot(x);access(y);splay(y);
}
inline void link(int x,int y)
{
makeroot(x);access(y);splay(y);
fa[x]=y;
Isum[y]^=sum[x];
pushup(y);
}
inline void cut(int x,int y)
{
split(x,y);
fa[x]=lc(y)=0;
pushup(y);
}
};
LCT T;
#undef lc
#undef rc
template<typename T> inline void read(T &x)
{
T data=0,w=1;
char ch=0;
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(ch>='0'&&ch<='9')data=((T)data<<3)+((T)data<<1)+(ch^'0'),ch=getchar();
x=data*w;
}
template<typename T> inline void write(T x,char c='\0')
{
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
if(c!='\0')putchar(c);
}
template<typename T> inline void chkmin(T &x,T y){x=(y<x?y:x);}
template<typename T> inline void chkmax(T &x,T y){x=(y>x?y:x);}
template<typename T> inline T min(T x,T y){return x<y?x:y;}
template<typename T> inline T max(T x,T y){return x>y?x:y;}
int main()
{
srand(time(0));
int id;
read(id);
read(n);read(m);
for(register int i=1;i<n;++i)
{
int u,v;
read(u);read(v);
T.link(u,v);
}
while(m--)
{
int opt;
read(opt);
if(opt==1)
{
int x,y,u,v;
read(x);read(y);read(u);read(v);
T.cut(x,y);T.link(u,v);
}
if(opt==2)
{
++Sn;
read(S[Sn].u);read(S[Sn].v);
S[Sn].w=rand();
Sval^=S[Sn].w;
T.access(S[Sn].u);T.splay(S[Sn].u);T.val[S[Sn].u]^=S[Sn].w;T.pushup(S[Sn].u);
T.access(S[Sn].v);T.splay(S[Sn].v);T.val[S[Sn].v]^=S[Sn].w;T.pushup(S[Sn].v);
}
if(opt==3)
{
int x;
read(x);
Sval^=S[x].w;
T.access(S[x].u);T.splay(S[x].u);T.val[S[x].u]^=S[x].w;T.pushup(S[x].u);
T.access(S[x].v);T.splay(S[x].v);T.val[S[x].v]^=S[x].w;T.pushup(S[x].v);
}
if(opt==4)
{
int x,y;
read(x);read(y);
T.makeroot(x);T.access(y);
if((T.Isum[y]^T.val[y])==Sval)puts("YES");
else puts("NO");
}
}
return 0;
}