2014.10.19各种强化练习并查集(虽然正题是排列组合。。。)
POJ的界面无语了,刷了一天的题,只有食物链是中文版。。。题意各种不解,各种坑爹翻译,各种脑补题意。
题目不按顺序排列,大致按难度升序排序;
先贴出并查集简单操作
初始化
1.POJ 1308 Is It A Tree?
大致题意:输入一组点之间的关系(儿子指向父亲),最后判断是否为一棵树(多组数据)
简单并查集操作,合并时判断两结点是否已在同一集合; 若有在同一集合的点或不为一棵树(遗漏点)。
code:
大致题意:一群学生聚成一团一团(一个学生可能在不同的团中),0号学生得了非典,输出几个学生得了非典(多组数据)。
简单并查及操作,左后扫一遍看几个学生在0号元素所在集合。
code:
1.POJ 1182 食物链
题意不说了,有中文版(好亲切),也算是并查集经典题之一吧,网上各种神牛题解,学会了其中一种(不喜勿喷):
扩展节点法:扩展节点数n变为3*n,a存储其本身,a+n存储a吃的东西,a+2*n存储吃a的东西;
节点大于n好判断,现在分析下两种情况
当输入为两种为同类时,判断两者是否存在吃与不吃关系即 find(y)!=find(x+n)&&find(y)!=find(x+2*n);
合并时三组合并 father[find(x)]=find(y); father[find(x+n)]=find(y+n); father[find(x+2*n)]=find(y+2*n);
当输入为两种为x吃y关系,判断两者是否存在其他关系时即find(y)!=find(x+2*n)&&find(y)!=find(x);
合并时依旧为三组 father[find(x+n)]=find(y); father[find(x)]=find(y+2*n); father[find(x+2*n)]=find(y+n);
code:
POJ的界面无语了,刷了一天的题,只有食物链是中文版。。。题意各种不解,各种坑爹翻译,各种脑补题意。
题目不按顺序排列,大致按难度升序排序;
先贴出并查集简单操作
初始化
</pre><pre name="code" class="cpp">for (i=1;i<=n;++i)
father[i]=i;查找int find(int x)//(路径压缩)
{
if (x!=father[x])
father[x]=find(father[x]);
return father[x];
}合并int union(int x,int y)
{
int r1,r2;
r1=find(x); r2=find(y);
if (r1!=r2)
father[r1]=r2;//(视具体情况合并关系不同)Easy1.POJ 1308 Is It A Tree?
大致题意:输入一组点之间的关系(儿子指向父亲),最后判断是否为一棵树(多组数据)
简单并查集操作,合并时判断两结点是否已在同一集合; 若有在同一集合的点或不为一棵树(遗漏点)。
code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int father[100001],root,use[100001];
int find(int x)
{
if (x!=father[x])
father[x]=find(father[x]);
return father[x];
}
int main()
{
int x,y,r1,r2,maxn,minn,t,i;
bool f;
t=0;
while ((x!=-1)||(t==0))
{
scanf("%d%d",&x,&y);
if (x==-1) break;
t++;
f=true;
memset(use,false,sizeof(use));
for (i=1;i<=100000;++i)
father[i]=i;
maxn=0; minn=100000;
while (x!=0)
{
r1=find(x); use[x]=true;
r2=find(y); use[y]=true;
maxn=max(maxn,max(x,y));
minn=min(minn,min(x,y));
if (r1!=r2)
father[r2]=r1;
else
f=false;
scanf("%d%d",&x,&y);
}
root=find(minn);
cout<<root<<endl;
for (i=minn+1;i<=maxn;++i)
if ((find(i)!=root)&&(use[i]))
{f=false; break;}
if (f)
cout<<"Case "<<t<<" is a tree."<<endl;
else
cout<<"Case "<<t<<" is not a tree."<<endl;
}
} 2.POJ 1611 The Suspects大致题意:一群学生聚成一团一团(一个学生可能在不同的团中),0号学生得了非典,输出几个学生得了非典(多组数据)。
简单并查及操作,左后扫一遍看几个学生在0号元素所在集合。
code:
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int father[30001],n,q,m;
int find(int x)
{
if (x!=father[x])
father[x]=find(father[x]);
return father[x];
}
int main()
{
int j,i,r1,r2,x,y,root,ans;
scanf("%d%d",&n,&q);
while (n!=0)
{
for (i=0;i<=n;++i)
father[i]=i;
for (j=1;j<=q;++j)
{
scanf("%d%d",&m,&x);
r1=find(x);
for (i=2;i<=m;++i)
{
scanf("%d",&y);
r2=find(y);
if (r1!=r2)
father[r2]=r1;
}
}
ans=0;
root=find(0);
for (i=0;i<=n;++i)
if (find(i)==root)
ans++;
cout<<ans<<endl;
scanf("%d%d",&n,&q);
}
}
3 POJ 2524 Ubiquitous Religions
大致题意 输入n个点之间的关系,最后输出有几个集合(多组数据)。
初始化集合数为结点数,每成功合并一次集合数就减一,最后输出即可。
code:
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int n,q,father[50001];
int find(int x)
{
if (x!=father[x])
father[x]=find(father[x]);
return father[x];
}
int main()
{
int i,r1,r2,x,y,ans,t;
t=0;
scanf("%d%d",&n,&q);
while (n!=0)
{
t++;
ans=n;
for (i=1;i<=n;++i)
father[i]=i;
for (i=1;i<=q;++i)
{
scanf("%d%d",&x,&y);
r1=find(x);
r2=find(y);
if (r1!=r2)
{
ans--;
father[r1]=r2;
}
}
cout<<"Case "<<t<<": "<<ans<<endl;
scanf("%d%d",&n,&q);
}
}
4.POJ 2236 Wireless Network
大致题意:东南亚大地震(神题意),电脑坏了,需要一台一台修,修好后可与一定欧拉距离D联网,询问i,j之间是否联网;多组数据
很裸的思路,每修好一个电脑与其距离小于D的且修好的电脑合并即可。
code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
bool rep[1002];
int father[1002],dis[1002][2],n,p;
int ola(int x,int y)
{
return (dis[x][0]-dis[y][0])*(dis[x][0]-dis[y][0])+(dis[x][1]-dis[y][1])
*(dis[x][1]-dis[y][1]);
}
int find(int x)
{
if (x!=father[x])
father[x]=find(father[x]);
return father[x];
}
int main()
{
int i,x,y,r1,r2;
char ch;
memset(rep,false,sizeof(rep));
scanf("%d%d",&n,&p);
for (i=1;i<=n;++i)
father[i]=i;
for (i=1;i<=n;++i)
scanf("%d%d",&dis[i][0],&dis[i][1]);
while (scanf("%*c%c",&ch)==1)
{
if (ch=='O')
{
scanf("%d",&x);
rep[x]=true;
for (i=1;i<=n;++i)
if ((ola(x,i)<=p*p)&&(rep[i]))
{
r1=find(x);
r2=find(i);
if (r1!=r2)
father[r1]=r2;
}
}
if (ch=='S')
{
scanf("%d%d",&x,&y);
r1=find(x);
r2=find(y);
if (r1!=r2)
printf("FAIL\n");
else
printf("SUCCESS\n");
}
}
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Normal:1.POJ 1182 食物链
题意不说了,有中文版(好亲切),也算是并查集经典题之一吧,网上各种神牛题解,学会了其中一种(不喜勿喷):
扩展节点法:扩展节点数n变为3*n,a存储其本身,a+n存储a吃的东西,a+2*n存储吃a的东西;
节点大于n好判断,现在分析下两种情况
当输入为两种为同类时,判断两者是否存在吃与不吃关系即 find(y)!=find(x+n)&&find(y)!=find(x+2*n);
合并时三组合并 father[find(x)]=find(y); father[find(x+n)]=find(y+n); father[find(x+2*n)]=find(y+2*n);
当输入为两种为x吃y关系,判断两者是否存在其他关系时即find(y)!=find(x+2*n)&&find(y)!=find(x);
合并时依旧为三组 father[find(x+n)]=find(y); father[find(x)]=find(y+2*n); father[find(x+2*n)]=find(y+n);
code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,k,father[150001];
int find(int x)
{
if (father[x]!=x)
father[x]=find(father[x]);
return father[x];
}
int main()
{
int i,kind,x,y,ans,r1,r2,r3,r4,r5,r6;
scanf("%d%d",&n,&k);
for (i=1;i<=3*n;++i)
father[i]=i;
ans=0;
for (i=1;i<=k;++i)
{
scanf("%d%d%d",&kind,&x,&y);
if ((x>n)||(y>n))
{ans++; continue;}
r1=find(x);
r2=find(y);
r3=find(x+n);
r4=find(y+n);
r5=find(x+2*n);
r6=find(y+2*n);
if (kind==1)
{
if ((r2==r3)||(r2==r5))
{
ans++;
continue;
}
else
{
father[r1]=r2;
father[r3]=r4;
father[r5]=r6;
}
}
if (kind==2)
{
if ((r1==r2)||(r5==r2))
{
ans++;
continue;
}
else
{
father[r3]=r2;
father[r5]=r4;
father[r1]=r6;
}
}
}
printf("%d\n",ans);
}
2.POJ 1703 Find them, Catch them
大致题意 输入n个节点,给出两者的敌人关系,左后判断两者是否为同一集合抑或无法判断(多组数据)
建一e数组,存储与i为敌人的集合代表,每读入一组数据,合并e[i]与j、e[j]与i即可,判断两点关系时,判断两点是否在同一集合,
若不在,则判断是否在其敌人集合,若依旧不在,则无法判断。
code:
你和你的朋友玩一个游戏。你的朋友写下来一连串的0或者1。你选择一个连续的子序列然后问他,这个子序列包含1的个数是奇数还是偶数。你的朋友回答完你的问题,接着你问下一个问题。 你怀疑你朋友的一些答案可能是错误的,你决定写一个程序来帮忙。程序将接受一系列你的问题及你朋友的回答,程序的目的是找到第一个错误的回答i,也就是存在一个序列满足前i-1个问题的答案,但是不满足前i个问题。
与银河英雄传说想法相似,但略简单,pa[i]存储i到其目前根结点距离(即为是奇是偶)(路径压缩亦可理解为其与父亲的距离),最后判断是否符合该条语句条件即可,数据范围较大,需离散化
code:
大致题意:给出一个节点与另一个节点相对位置关系,最后输完在输入询问(时间即为第几条语句),输出两点之间曼哈顿距离(未联通输出-1)。
想法与上题基本类似,但是pa[i]存储其与其父亲的曼哈顿距离,公式较难推出;(ps:离线处理太麻烦了)。
code:
题意 几乎与银河英雄传说一样,飞船变成盒子之类的东西;
son[i]存储以i为根有几个孩子,pa[i]存储其到父亲的距离,不断维护即可;(这道题与前两道题模版几乎一样);
code:
大致题意 输入n个节点,给出两者的敌人关系,左后判断两者是否为同一集合抑或无法判断(多组数据)
建一e数组,存储与i为敌人的集合代表,每读入一组数据,合并e[i]与j、e[j]与i即可,判断两点关系时,判断两点是否在同一集合,
若不在,则判断是否在其敌人集合,若依旧不在,则无法判断。
code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int father[100001],gang[100001],t,n,m;
int find(int x)
{
if (x!=father[x])
father[x]=find(father[x]);
return father[x];
}
int main()
{
int i,j,x,y,r1,r2;
char kind;
scanf("%d",&t);
for (i=1;i<=t;++i)
{
scanf("%d%d",&n,&m);
for (j=0;j<=n;++j)
father[j]=j;
memset(gang,0,sizeof(gang));
for (j=1;j<=m;++j)
{
scanf("%*c%c",&kind);
scanf("%d%d",&x,&y);
r1=find(x);
r2=find(y);
if (kind=='D')
if (r1!=r2)
{
if (gang[r1]==0)
gang[r1]=r2;
else
if (find(gang[r1])!=r2)
father[find(gang[r1])]=r2;
if (gang[r2]==0)
gang[r2]=r1;
else
if (find(gang[r2])!=r1)
father[find(gang[r2])]=r1;
}
if (kind=='A')
{
if (r1==r2)
cout<<"In the same gang."<<endl;
if (r1!=r2)
{
if (find(gang[r1])!=r2)
cout<<"Not sure yet."<<endl;
else
cout<<"In different gangs."<<endl;
}
}
}
}
}
3. POJ 2492 A Bug's Life
大致题意 给出n组昆虫关系(是否可交配),若出现矛盾则输出有同性恋者(严重吐槽此题题意)(多组数据);
一个看似高大上的题意实际与上一题一模一样,均为合并e[i]与j、e[j]和i,最后判断即可;
code:
大致题意,直接上codevs的题面: 大致题意 给出n组昆虫关系(是否可交配),若出现矛盾则输出有同性恋者(严重吐槽此题题意)(多组数据);
一个看似高大上的题意实际与上一题一模一样,均为合并e[i]与j、e[j]和i,最后判断即可;
code:
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int t,father[2001],e[2001],n,q;
int find(int x)
{
if (x!=father[x])
father[x]=find(father[x]);
return father[x];
}
int main()
{
int i,j,r1,r2,r3,x,y;
bool f;
scanf("%d",&t);
for (i=1;i<=t;++i)
{
scanf("%d%d",&n,&q);
memset(e,0,sizeof(e));
for (j=1;j<=n;++j)
father[j]=j;
f=true;
for (j=1;j<=q;++j)
{
scanf("%d%d",&x,&y);
r1=find(x); r2=find(y);
if (r1==r2)
f=false;
else
{
if (e[r1]==0)
e[r1]=r2;
else
{
r3=find(e[r1]);
if (r2!=r3)
father[r2]=r3;
}
if (e[r2]==0)
e[r2]=r1;
else
{
r3=find(e[r2]);
if (r1!=r3)
father[r1]=r3;
}
}
}
if (f)
{
cout<<"Scenario #"<<i<<':'<<endl;
cout<<"No suspicious bugs found!"<<endl<<endl;
}
else
{
cout<<"Scenario #"<<i<<':'<<endl;
cout<<"Suspicious bugs found!"<<endl<<endl;
}
}
}4.POJ 1733 Parity game(codevs 奇偶游戏,tyvj和vijos 小胖的奇偶)你和你的朋友玩一个游戏。你的朋友写下来一连串的0或者1。你选择一个连续的子序列然后问他,这个子序列包含1的个数是奇数还是偶数。你的朋友回答完你的问题,接着你问下一个问题。 你怀疑你朋友的一些答案可能是错误的,你决定写一个程序来帮忙。程序将接受一系列你的问题及你朋友的回答,程序的目的是找到第一个错误的回答i,也就是存在一个序列满足前i-1个问题的答案,但是不满足前i个问题。
与银河英雄传说想法相似,但略简单,pa[i]存储i到其目前根结点距离(即为是奇是偶)(路径压缩亦可理解为其与父亲的距离),最后判断是否符合该条语句条件即可,数据范围较大,需离散化
code:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
long long l,n;
int father[10001],pa[10001]={0};
long long a[10001],b[10001],kind[5001];
int find(int x)
{
int j;
if (x!=father[x])
{
j=find(father[x]);
pa[x]+=pa[father[x]]; //更新距离;
father[x]=j;
}
return
father[x];
}
int main()
{
int i,x,y,r1,r2,size;
bool f=false;
char s[10];
scanf("%lld%d",&l,&n);
for (i=1;i<=n;++i)
{
scanf("%lld%lld",&a[i*2-1],&a[i*2]);
a[i*2-1]--;
b[i*2-1]=a[i*2-1]; b[i*2]=a[i*2];
scanf("%s",&s);
if (s[0]=='e')
kind[i]=0;
if (s[0]=='o')
kind[i]=1;
}
sort(b+1,b+2*n+1);
size=unique(b+1,b+2*n+1)-b-1;
for (i=1;i<=2*n;++i)
a[i]=upper_bound(b+1,b+size+1,a[i])-b-1;// 这之上为离散化
for (i=1;i<=size;++i)
father[i]=i;
for (i=1;i<=n;++i)
{
x=a[2*i-1]; y=a[2*i];
r1=find(x); r2=find(y);
if (r1!=r2)
{
father[r2]=r1;
pa[r2]=pa[x]+kind[i]-pa[y]; //关键语句
}
if (r1==r2)
{
if (abs(pa[y]-pa[x])%2!=kind[i])
{
cout<<i-1<<endl;
f=true;
break;
}
}
}
if (f==false)
cout<<n<<endl;
} 5.POJ 1984 Navigation Nightmare大致题意:给出一个节点与另一个节点相对位置关系,最后输完在输入询问(时间即为第几条语句),输出两点之间曼哈顿距离(未联通输出-1)。
想法与上题基本类似,但是pa[i]存储其与其父亲的曼哈顿距离,公式较难推出;(ps:离线处理太麻烦了)。
code:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
long long l,n;
int father[10001],pa[10001]={0};
long long a[10001],b[10001],kind[5001];
int find(int x)
{
int j;
if (x!=father[x])
{
j=find(father[x]);
pa[x]+=pa[father[x]]; //更新距离;
father[x]=j;
}
return
father[x];
}
int main()
{
int i,x,y,r1,r2,size;
bool f=false;
char s[10];
scanf("%lld%d",&l,&n);
for (i=1;i<=n;++i)
{
scanf("%lld%lld",&a[i*2-1],&a[i*2]);
a[i*2-1]--;
b[i*2-1]=a[i*2-1]; b[i*2]=a[i*2];
scanf("%s",&s);
if (s[0]=='e')
kind[i]=0;
if (s[0]=='o')
kind[i]=1;
}
sort(b+1,b+2*n+1);
size=unique(b+1,b+2*n+1)-b-1;
for (i=1;i<=2*n;++i)
a[i]=upper_bound(b+1,b+size+1,a[i])-b-1;// 这之上为离散化
for (i=1;i<=size;++i)
father[i]=i;
for (i=1;i<=n;++i)
{
x=a[2*i-1]; y=a[2*i];
r1=find(x); r2=find(y);
if (r1!=r2)
{
father[r2]=r1;
pa[r2]=pa[x]+kind[i]-pa[y]; //关键语句
}
if (r1==r2)
{
if (abs(pa[y]-pa[x])%2!=kind[i])
{
cout<<i-1<<endl;
f=true;
break;
}
}
}
if (f==false)
cout<<n<<endl;
} 6.poj 1988 Cube Stacking题意 几乎与银河英雄传说一样,飞船变成盒子之类的东西;
son[i]存储以i为根有几个孩子,pa[i]存储其到父亲的距离,不断维护即可;(这道题与前两道题模版几乎一样);
code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int father[30001],son[30001],n,pa[30001];
int find(int x)
{
int j;
if (x!=father[x])
{
j=find(father[x]);
pa[x]=pa[father[x]]+pa[x]; //维护核心代码,由于路径压缩,其到父亲距离不一定为1;
father[x]=j;
}
return father[x];
}
int main()
{
int i,x,y,r1,r2,r;
char ch;
for (i=1;i<=30000;++i)
{
father[i]=i;
son[i]=1;
pa[i]=0;
}
scanf("%d",&n);
for (i=1;i<=n;++i)
{
scanf("%*c%c",&ch);
if (ch=='M')
{
scanf("%d%d",&x,&y);
r1=find(x);
r2=find(y);
pa[r2]=son[r1];//当前要合并节点到父亲距离即为son个数;
son[r1]=son[r2]+son[r1]; //更新孩子数信息;
father[r2]=r1;
}
if (ch=='C')
{
scanf("%d",&x);
r=find(x);
cout<<son[r]-pa[x]-1<<endl;
}
}
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Hard:
唯一一道 POJ 2546 Rochambeau
大致题意 有一群小朋友玩剪子石头布,分成3组,每一组都只能出一个手势,但有一个是裁判, 可以任意手势(这是裁判。。。),最后输处在第几条语句得出谁为裁判,可能有多个裁判时,输出Can not determine,可以无裁判时,输出Impossible;
易想出穷举裁判是谁,排除裁判后按食物链做即可 ,若出现矛盾则此人不可能为裁判,判断某人不可能为裁判的最大语句数即为判断谁是裁判的步数(排除最后另外一人为裁判的可能性),想出算法code呼之欲出;
code:
Hard:
唯一一道 POJ 2546 Rochambeau
大致题意 有一群小朋友玩剪子石头布,分成3组,每一组都只能出一个手势,但有一个是裁判, 可以任意手势(这是裁判。。。),最后输处在第几条语句得出谁为裁判,可能有多个裁判时,输出Can not determine,可以无裁判时,输出Impossible;
易想出穷举裁判是谁,排除裁判后按食物链做即可 ,若出现矛盾则此人不可能为裁判,判断某人不可能为裁判的最大语句数即为判断谁是裁判的步数(排除最后另外一人为裁判的可能性),想出算法code呼之欲出;
code:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
struct hp{
int x,y;
char kind;
}a[2001];
int father[1501],n,m;
int find(int x)
{
if (x!=father[x])
father[x]=find(father[x]);
return father[x];
}
int main()
{
int i,j,jdg,r1,r2,r3,r4,r5,r6,x,y,maxn,l,k,ans,kind;
char set[1000];
bool f;
while (scanf("%d%d",&n,&m)==2)
{
if ((n==1)&&(m==0))
cout<<"Player 0 can be determined to be the judge after 0 lines"<<endl;
else
{
ans=0; maxn=0;
memset(a,0,sizeof(a));
for (j=1;j<=m;++j)
{
scanf("%s",&set);
l=strlen(set);
k=0;
x=0; y=0;
while (set[k]>='0'&&set[k]<='9')
{
x=x*10+(int)(set[k])-48;
k++;
}
kind=set[k];
k++;
while (k<l)
{
y=y*10+(int)(set[k])-48;
k++;
}
a[j].x=x; a[j].kind=kind; a[j].y=y;
}
for (i=0;i<=n-1;++i)
{
f=true;
for (j=0;j<=3*n;++j)
father[j]=j;
for (j=1;j<=m;++j)
{
x=a[j].x; y=a[j].y; kind=a[j].kind;
if ((x!=i)&&(y!=i))
{
if (kind=='<')
{
swap(x,y);
kind='>';
}
r1=find(x);
r2=find(y);
r3=find(x+n);
r4=find(y+n);
r5=find(x+2*n);
r6=find(y+2*n);
if (kind=='=')
{
if ((r2==r3)||(r2==r5))
{
maxn=max(maxn,j);
f=false;
break;
}
else
{
father[r2]=r1;
father[r3]=r4;
father
[r5]=r6;
}
}
if (kind=='>')
{
if ((r2==r5)||(r1==r2))
{
maxn=max(maxn,j);
f=false;
break;
}
else
{
father[r3]=r2;
father[r5]=r4;
father[r1]=r6;
}
}
}
}
if (f)
{
if (ans>0)
{
cout<<"Can not determine"<<endl;
ans++;
break;
}
if (ans==0)
{ans++; jdg=i;}
}
}
if (ans==1)
cout<<"Player "<<jdg<<" can be determined to be the judge after "<<maxn<<" lines"<<endl;
if (ans==0)
cout<<"Impossible"<<endl;
}
}
}|
------------------------------------------------------------------------------------------------------------------------------- 感谢同机房神牛TA,Rivendile,yangfangyuan; lcomyn 2014.10.19 |
本文通过多道POJ题目详细解析并查集算法的应用场景与实现技巧,涵盖基础操作到复杂问题解决。
2216

被折叠的 条评论
为什么被折叠?



