并查集进阶
先假设读者都已经会最基本的并查集了(模板&最小生成树)
1.维护元素间的“不同”关系
例题:传送门
题意:城市里有两个黑帮,虎帮\和龙帮。现在有
n
(
n
≤
1
0
5
)
n(n\leq10^5)
n(n≤105)个罪犯,不是在龙帮就是在虎帮。已知
m
(
m
≤
1
0
5
)
m(m\leq 10^5)
m(m≤105)个关系以如下方式描述:(a,b表示两个人的编号)
- D a,b:a,b属于不同的帮派
- A a,b:询问a,b间的关系:
- 同一帮派:输出"In the same gang."
- 不同帮派:输出"In different gangs."
- 目前还不确定:输出"Not sure yet."
有 T ( T ≤ 20 ) T(T\leq 20) T(T≤20)组询问
题解:考虑并查集:find(u)==find(v):u,v在同一个帮派中
另建立一个数组p[i],表示i有一个敌人是p[i]
有个小问题:为什么只要保存一个敌人?因为i的敌人在同一个集合中。
合并的时候
- 如果a还没有确定敌人,令p[a]=b
- 如果a已经确定了敌人,合并p[a],b
b同理。
最后关系的判断可见代码:
/**********/
#define MAXN 100011
struct ufs//封装良好的并查集,给个赞呗
{
ll fa[MAXN];
void build(ll n)
{
for(ll i=1;i<=n;++i)fa[i]=i;
}
ll find(ll x)
{
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
void uni(ll u,ll v)
{
u=find(u),v=find(v);
if(u==v)return;
fa[u]=v;
}
bool same(ll u,ll v)
{
u=find(u),v=find(v);
return u==v;
}
}s;
ll p[MAXN];
void work()
{
ll n=read(),m=read();//快读被省略
s.build(n);
memset(p,0,sizeof p);
for(ll i=1;i<=m;++i)
{
char c=getchar();
while(c!='A'&&c!='D')c=getchar();
ll u=read(),v=read();
if(c=='A')
{
if(s.same(u,v))puts("In the same gang.");
else if((p[u]&&s.same(p[u],v))||(p[v]&&s.same(p[v],u)))puts("In different gangs.");//敌人与对方在同一集合,说明自己和对方是不同集合(帮派)
else puts("Not sure yet.");
}
else
{
if(p[u])s.uni(p[u],v);
else p[u]=v;
if(p[v])s.uni(p[v],u);
else p[v]=u;
}
}
}
int main()
{
ll t=read();
while(t--)work();
return 0;
}