种类并查集
先来个最水的
poj 2524 Ubiquitous Religions
http://poj.org/problem?id=2524
【题意】:给出n个大学生,其中有m对宗教信仰相同的学生,请你估算这n个学生中最多有多少种宗教信仰。
【思路】: 先设 答案为n 每合并一个集合减一就ok了
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 #include <math.h> 5 using namespace std; 6 7 8 int n; 9 int f[50002]; 10 11 void init() 12 { 13 for(int i=0;i<=n;i++) 14 f[i]=i; 15 16 } 17 18 19 int find(int x) 20 { 21 return f[x]==x?x:f[x]=find(f[x]); 22 } 23 24 int main() 25 { 26 int i,j,a,b,m,p=1; 27 while(scanf("%d%d",&n,&m)) 28 { 29 if(n==0&&m==0) 30 break; 31 init(); 32 int ans=n; 33 while(m--) 34 { 35 scanf("%d%d",&a,&b); 36 int aa,bb; 37 aa=find(a); 38 bb=find(b); 39 if(aa!=bb) 40 { 41 f[aa]=bb; 42 ans--; 43 } 44 } 45 printf("Case %d: %d\n",p++,ans); 46 } 47 48 return 0; 49 }
poj 1611 The Suspects
http://poj.org/problem?id=1611
【题意】:N个组的学生,一个学生能同时加入不同的组,同一组的学生会同时感染病毒,现在0号的学生感染了病毒,问一共有多少个人感染病毒。
【思路】 并查集 若是两个 学生在的集合不同(根节点不同)就 合并 只要把一个根节点变成另一个根节点的father 同时新的根节点的 sum值 为两集合之和 就好了 在find x的时候 ,找根节点的同时更新sum[x]的值 (从离根最近的开始)
1 #include<iostream> 2 #include<stdio.h> 3 using namespace std; 4 5 int f[30002],sum[30002],n; 6 7 void init() 8 { 9 for(int i=0;i<=n;i++) 10 { 11 sum[i]=1;f[i]=i; 12 } 13 } 14 15 int find(int x) 16 { 17 if(f[x]==x) 18 return x; 19 f[x]=find(f[x]); 20 sum[x]=sum[f[x]]; 21 return f[x]; 22 } 23 24 void lian(int tx,int ty) 25 { 26 f[ty]=tx; 27 sum[tx]+=sum[ty]; 28 } 29 30 int main() 31 { 32 int i,j,m,t,num,a,b; 33 while(~scanf("%d%d",&n,&m)) 34 { 35 if(n==0&&m==0) 36 break; 37 init(); 38 while(m--) 39 { 40 scanf("%d",&num); 41 scanf("%d",&a); 42 num--; 43 while(num--) 44 { 45 scanf("%d",&b); 46 int aa,bb; 47 aa=find(a); 48 bb=find(b); 49 if(aa!=bb) 50 lian(aa,bb); 51 } 52 } 53 printf("%d\n",sum[find(0)]); 54 } 55 }
poj 1703
【题意】 有两个帮派,现在告诉你一些互相敌对的点对,然后让你判断某两个点之间的关系
【思路】只要 每个节点 多设一项 kind 表示和根节点的关系是相同还是不同 相同为0 不同为1 输入敌对的 关系时
1 #include<iostream> 2 #include<stdio.h> 3 using namespace std; 4 5 struct node{int kind; int fa;}no[100002]; 6 int n; 7 8 void init() 9 { 10 for(int i=0;i<=n;i++) 11 { 12 no[i].fa=i; no[i].kind=0; 13 } 14 } 15 16 int find(int x) 17 { 18 if(no[x].fa==x) 19 return x; 20 int t; 21 t=find(no[x].fa) 22 // 这里执行完 x.fa 的fa和kind都求出来了 fa是 这个集合的根节点 kind是x.fa与根节点的关系 23 // 而此时 x.fa 和x.kind 都未更新 下面就是 根据 x与x.fa的关系和 x.fa与根节点的关系 推出 x与根节点的关系 24 no[x].kind^=no[no[x].fa].kind; 25 return no[x].fa=t; 26 } 27 28 int main() 29 { 30 int i,j,m,t,a,b; 31 char c[10]; 32 scanf("%d",&t); 33 while(t--) 34 { 35 36 scanf("%d%d",&n,&m); 37 init(); 38 while(m--) 39 { 40 scanf("%s%d%d",c,&a,&b); 41 int aa,bb; 42 aa=find(a); 43 bb=find(b); 44 if(c[0]=='D') 45 { 46 no[aa].fa=bb; 47 no[aa].kind=no[a].kind^no[b].kind^1;//aa与 bb 的关系 有a与aa的关系 和b与bb的关系推出 48 } 49 else 50 { 51 if(aa!=bb) 52 printf("Not sure yet.\n"); 53 else 54 { 55 if(no[a].kind==no[b].kind) 56 printf("In the same gang.\n"); 57 else 58 printf("In different gangs.\n"); 59 } 60 } 61 } 62 } 63 }
1 #include<iostream> 2 #include<stdio.h> 3 #include<string.h> 4 using namespace std; 5 6 int f[200020],sum[200020]; 7 8 void init(int n) 9 { 10 memset(sum,0,sizeof(sum)); 11 for(int i=0;i<=n;i++) 12 f[i]=i; 13 14 } 15 16 int find(int x) 17 { 18 if(f[x]==x) 19 return x; 20 int t=f[x]; 21 f[x]=find(f[x]); 22 sum[x]+=sum[t]; 23 return f[x]; 24 } 25 26 void lian(int x,int y,int a,int b,int v) 27 { 28 if(x > y) 29 { 30 f[y] = x; 31 sum[y] = sum[a]-v-sum[b]; 32 }else 33 { 34 f[x] = y; 35 sum[x] = v + sum[b] - sum[a]; 36 } 37 } 38 39 int main() 40 { 41 int i,j,m,n,t,a,b,aa,bb,d; 42 while(scanf("%d%d",&n,&m)!=EOF) 43 { 44 45 init(n); 46 int num=0; 47 while(m--) 48 { 49 scanf("%d%d%d",&a,&b,&d); 50 a--; 51 int aa,bb; 52 aa=find(a); 53 bb=find(b); 54 if(aa==bb&&sum[a]!=sum[b]+d) 55 num++; 56 else if(aa!=bb) 57 lian(aa,bb,a,b,d); 58 59 } 60 printf("%d\n",num); 61 } 62 return 0; 63 }
poj 1988 Cube Stacking
http://poj.org/problem?id=1988
【题意】 有n个元素,开始每个元素自己一栈,有两种操作,将含有元素x的栈放在含有y的栈的顶端,合并为一个栈。第二种操作是询问含有x元素下面有多少个元素。
【思路】每个节点除了一项fa 还增加一项d 和一项 sum的表示的是这个节点下面的元素个数 也就是所求。 sum表示的是这个节点所在的集合的节点数
根节点的d值总是0 。
hdu 3038 How Many Answers Are Wrong
http://acm.hdu.edu.cn/showproblem.php?pid=3038
【题意】 给定区间和 判定那个说法是错误的
【思路】刚开始没做过种类并查集 做这个题目 半天没想明白。。
sum[x]表示的是x到 x所在集合的根节点的区间和
下面应用别人的一段话:
如果我们知道a到b之间的关系,a到c之间的关系,那么我们就可以知道a,b,c任意两个之间的关系,如果我们再知道了d和c之间的关系,那么我们就知道了a,b,c,d之间的关系,但是怎么表示这些关系呢??我们用的是并查集,顺便加一个每一个节点到根的距离,这样的话,任意两个点之间关系就可以通过求与根的距离求差得出,也就是说,如果输入的n,m在一个集合里,那么我们判断这两个的关系是否和已有的冲突,如果n,m不在一个集合里,那么我们就合并这两个集合,是的n,m这两个所在的两个集合之间的任意元素都有关系。
合并的函数
void lian(int x,int y,int a,int b,int v) { if(x > y) { f[y] = x; sum[y] = sum[a]-v-sum[b]; }else { f[x] = y; sum[x] = v + sum[b] - sum[a]; } }
这个怎么来的呢 我们始终选 较小根节点的最为 新的根节点
当y<x时 sum[x]-sum[b]+v+sum[a]=0 即 sum[x]=sum[b]-v-sum[a]

当x<y 时 sum[y]-sum[a]-v+sum[b]=0 即
sum[y] = sum[a]-v-sum[b];

#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int f[200020],sum[200020]; void init(int n) { memset(sum,0,sizeof(sum)); for(int i=0;i<=n;i++) f[i]=i; } int find(int x) { if(f[x]==x) return x; int t; t=find(f[x]); sum[x]+=sum[f[x]];
// find(f[x]) 已执行完 在这里 sum[f[x]]表示的是 f[x]到根节点的距离 而 find(x)还未执行完 sum[x]是 x到f[x]的距离
// x 到根节点的值 为sum[f[x]]+ sum[x]
return f[x]=t; } void lian(int x,int y,int a,int b,int v) { if(x > y) { f[x] = y; sum[x] = sum[b] - sum[a]-v; }else { f[y] = x; sum[y] = sum[a]+v-sum[b]; } } int main() { int i,j,m,n,t,a,b,aa,bb,d; while(scanf("%d%d",&n,&m)!=EOF) { init(n); int num=0; while(m--) { scanf("%d%d%d",&a,&b,&d); a--; int aa,bb; aa=find(a); bb=find(b); if(aa==bb&&sum[b]!=sum[a]+d) num++; else if(aa!=bb) lian(aa,bb,a,b,d); } printf("%d\n",num); } return 0; }
poj 2492 A Bug's Life
http://poj.org/problem?id=2492
【题意】 给出m对在一起的虫子 看里面有没有同性恋
和1703 差不多
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 #include <math.h> 5 using namespace std; 6 struct node{int fa,kind;}no[2002]; 7 8 void init(int n) 9 { 10 for(int i=0;i<=n;i++) 11 { 12 no[i].fa=i; 13 no[i].kind=0; 14 } 15 } 16 17 int find(int x) 18 { 19 if (no[x].fa==x) 20 return x; 21 int t; 22 t=find(no[x].fa); 23 no[x].kind^=no[no[x].fa].kind; 24 return no[x].fa=t; 25 26 } 27 28 int main() 29 { 30 int i,t,j,a,b,m,n,p=1; 31 scanf("%d",&t); 32 while(t--) 33 { 34 int flag=0; 35 36 scanf("%d%d",&n,&m); init(n); 37 while(m--) 38 { 39 scanf("%d%d",&a,&b); 40 int aa,bb; 41 aa=find(a); 42 bb=find(b); 43 if(aa!=bb) 44 { 45 no[aa].fa=bb; 46 no[aa].kind=no[a].kind^no[b].kind^1; 47 } 48 else 49 { 50 if(no[a].kind==no[b].kind) 51 flag=1; 52 } 53 } 54 printf("Scenario #%d:\n",p++); 55 if(flag) 56 printf("Suspicious bugs found!\n"); 57 else printf("No suspicious bugs found!\n"); 58 printf("\n"); 59 } 60 61 return 0; 62 }
poj 1456
【题意】买卖N件东西,每件东西都有个截止时间和价值,在截止时间之前买都可以,而每个单位时间只能买一件。问最大获利
【思路】 这个我还真的没想到要用并查集 。。 还是看了别人的博客 将物品按照价格从高到底的顺序排列,购买一个就在时间点上做一个标记,只要不冲突就可以购买 我们把连续的被占用的区间看成一个集合(子树),它的根结点为这个区间左边第一个未被占用的时间点。
先排序,然后每次判断Find(b[i])是否大于0,大于0说明左边还有未被占用的空间,则占用它,然后合并(b[i], Find(b[i]) – 1)即可
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 #include <math.h> 5 #include<algorithm> 6 using namespace std; 7 struct node{int d;int num;}no[10002]; 8 int f[10002]; 9 int cmp(node a,node b) 10 { 11 return a.num>b.num; 12 } 13 14 int find(int x) 15 { 16 return f[x]==x?x:f[x]=find(f[x]); 17 } 18 int main() 19 { 20 int i,j,n,m,t; 21 while(scanf("%d",&n)!=EOF) 22 { 23 for(i=0;i<n;i++) 24 scanf("%d%d",&no[i].num,&no[i].d); 25 sort(no,no+n,cmp); 26 for(i=0;i<10005;i++) 27 f[i]=i; 28 int ans=0; 29 for(i=0;i<n;i++) 30 { 31 int t=find(no[i].d); 32 if(t>0) 33 { 34 ans+=no[i].num; 35 f[t]=t-1; 36 } 37 } 38 printf("%d\n",ans); 39 } 40 }
浙公网安备 33010602011771号