您的位置:首页 > 其它

[NOIp复习计划]:并查集

2017-07-17 00:31 253 查看
[bzoj 1370] [Baltic2003]Gang团伙

把一个点x拆成两个点x1,x2,与x1合并就代表是朋友,与x2合并就表示是敌人,这样处理的好处就是,假设y,z是x的敌人,那么把x2,与y,z分别合并时,y与x也就间接合并了,最后统计答案就好了。

#include<cstdio>
#include<algorithm>
using namespace std;
int fa[100010],a[100010];
int n,m,x,y,ans;
char ch[3];
inline int getfa(int x)
{return fa[x]==x?x:fa[x]=getfa(fa[x]);}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=2*n;i++)fa[i]=i;
for (int i=1;i<=m;i++)
{
scanf("%s%d%d",ch+1,&x,&y);
if (ch[1]=='F')fa[getfa(x)]=getfa(y);
else
{
fa[getfa(x)]=getfa(n+y);
fa[getfa(y)]=getfa(n+x);
}
}
for(int i=1;i<=n;i++)a[i]=getfa(i);
sort(a+1,a+n+1);
ans=1;
for(int i=2;i<=n;i++)if(a[i]!=a[i-1])ans++;
printf("%d\n",ans);
return 0;
}


[bzoj 1529][POI2005]ska Piggy banks

把钥匙的包含关系看成边,那么就形成了好多联通块,每个联通块中只要敲掉一个,每个就都可以访问到,维护联通块用并查集啦。

#include<bits/stdc++.h>
#define N 1000005
using namespace std;
int n,ans;
int fa
;
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
fa[i]=i;
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);
int p=find(i),q=find(x);
if(p!=q)fa[q]=i;
}
for(int i=1;i<=n;i++)
if(fa[i]==i)ans++;
printf("%d",ans);
return 0;
}



[bzoj 1116][POI2008]CLO

好吧先补一下基础知识,一个节点的入度和出度是在有向图中定义的,无向图并没有这种说法,于是树形结构是题意不满足的。

根据上面的定义我们可以知道,只要有个环就可以满足题意,可以脑补一下,对于每个极小环,我们可以按顺序构造有向边,顺时针或逆时针,当然环上每个点不一定只有一条边,对于那些边,我们可以选择把它设为方向向外指的有向边去连接一个树形结构,或是直接保留无向边(不对入度有贡献),去连接另一个环。

当然图也有不联通的现象。

这样题目就转化成了判断每个联通块中是否有环。

判断的方法,是在每个集合的根处维护一个bool值,代表该集合是否有环。

例如:合并x,y时,如果x,y在同一集合就把根的值设为true,不再一个集合,就把y并向x,根的值为x根的值|y根的值。

#include<cstdio>
const int MAXN = 100010;
int fa[MAXN];
bool tag[MAXN];
int getroot(int x){
return fa[x]==x?x:fa[x]=getroot(fa[x]);
}
void unite(int x,int y){
int f_x=getroot(x);
int f_y=getroot(y);
if(f_x==f_y){
tag[f_x]=1;
}else{
fa[f_x]=f_y;
tag[f_y]|=tag[f_x];
}
}

int main(){
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++){
int a,b;
scanf("%d %d",&a,&b);
unite(a,b);
}
for(int i=1;i<=n;i++){
if(fa[i]==i && tag[i]==0){
puts("NIE");
return 0;
}
}
puts("TAK");
return 0;
}


[bzoj 1015][JSOI2008]星球大战starwar

在线的方法不好想。感觉按秩合并并查集可做

考虑离线做法,一开就把所有路炸掉,然后倒着做。

这就变成了,每次合并两个集合,并维护联通块的个数,就可以随便搞了。

今年1月写的代码风格怎么跟现在差那么多

#include<cstdio>
#define rep(i,a,b) for(i=a;i<=b;i++)
#define dep(i,a,b) for(i=a;i>=b;i--)
#define maxn  200010
int fa[maxn<<1],h[maxn<<1],nx[maxn<<2],to[maxn<<2],tm[maxn<<1],ans[maxn<<1],mk[maxn<<1],len,m,n,cnt,cntOfTm;
int getroot(int x){
return fa[x]==x?x:fa[x]=getroot(fa[x]);
}
void Find(int &ret,int nd){
ret=getroot(nd);
}
#define Merge(aaa,bbb) {int ra,rb;Find(ra,aaa);Find(rb,bbb);if(ra!=rb)cnt--;fa[rb]=ra;}
#define Init(){int ii;rep(ii,1,n)fa[ii]=ii;}
#define AddEdge(a,b){len++;nx[len]=h[a];to[len]=b;h[a]=len;}

int main()
{
int i,j,k,a,b;
scanf("%d%d",&n,&m);
Init();
rep(i,1,m)
{
scanf("%d%d",&a,&b);a++;b++;
AddEdge(a,b);AddEdge(b,a);
}
scanf("%d",&cntOfTm);
rep(i,1,cntOfTm){
scanf("%d",&a);a++;tm[a]=1;mk[i]=a;
}
rep(i,1,n)
{

if(tm[i]!=0) continue;
cnt++;
for(j=h[i];j;j=nx[j])
{
if(tm[to[j]]==0)
{
Merge(i,to[j]);
}
}
}
ans[cntOfTm]=cnt;
dep(i,cntOfTm,1)
{
tm[mk[i]]=0;cnt++;
for(j=h[mk[i]];j;j=nx[j])
{

if(tm[to[j]]==0)
{
Merge(to[j],mk[i]);

}
}
ans[i-1]=cnt;
}
rep(i,0,cntOfTm)
printf("%d\n",ans[i]);
return 0;
}


[bzoj 1854][Scoi2010]游戏

是个裸的二分图模型

我们把属性看成点,把装备看成边,这样对于每个联通块,假设有n个点,如果有环,那么n个都可以用,而如果没有环,那就只能有n-1个。

下面是令人称奇的微操。并没有

vis[i]表示i能不能用

设当前要合并两个x,y,x<y

如果它们不属于一个集合,那么比较x的根和y的根的大小,把小的并向大的,然后把小的根的vis设为true。

这是因为我们要维护每个集合的根是该集合内元素的极大值,这样方便更新vis的值。

而如果它们在一个集合里,那么就把根的vis设为true就好了,这是因为这个联通块里有环,所以最大值就可以取了。

最后从1-100000看看vis,更新答案就好了。

#include<cstdio>
#define rep(i,a,b) for(i=a;i<=b;i++)
#define dep(i,a,b) for(i=a;i>=b;i--)
const int MAXN = 1000005;
int n,fa[MAXN];
bool right[MAXN];
int GetRoot(int x){return fa[x]==x?x:fa[x]=GetRoot(fa[x]);}
void solve(int x,int y){
int Fx=GetRoot(x),Fy=GetRoot(y);
if(Fx==Fy){right[Fy]|=1;}
else{if(Fx>Fy){int t=Fx;Fx=Fy;Fy=t;}fa[Fx]=Fy;right[Fx]|=1;}
}
int main(){
int i,a,b;
scanf("%d",&n);
rep(i,1,100006)fa[i]=i;
rep(i,1,n){
scanf("%d%d",&a,&b);
solve(a,b);
}
rep(i,1,100005){
if(!right[i]){
printf("%d",i-1);
return 0;
}
}
return 0;
}



[bzoj 1202][HNOI2005]狡猾的商人

把每个月看成点,因为要维护值,考虑带权并查集,每个节点多维护一个值表示与父节点值的差值。

然后乱搞就行了233.

#include<cstdio>
#define rep(i,a,b) for(i=a;i<=b;i++)
#define dep(i,a,b) for(i=a;i>=b;i--)
#define MAXN 120
int fa[MAXN],val[MAXN],t,n,m;
void ReSet(){int i;rep(i,0,MAXN-1)fa[i]=i,val[i]=0;}
int GetRoot(int x){if(fa[x]==x)return fa[x];int k=GetRoot(fa[x]);val[x]+=val[fa[x]];fa[x]=k;return k;}
int main(){
int i,a,b,c;
bool flag=0;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
ReSet();
flag=0;
rep(i,1,m){
scanf("%d%d%d",&a,&b,&c);a--;
int Fa=GetRoot(a),Fb=GetRoot(b);

if(Fa==Fb){
if(val[b]-val[a]!=c && flag==0){
puts("false");
flag=1;
}
}else{
fa[Fa]=Fb;
val[Fa]=val[b]-val[a]-c;
}
}
if(!flag)puts("true");
}
return 0;
}


to be continued……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: