并查集:
N 个不同的元素分布在若干个互不相交集合
中,需要进行以下3个操作:
1. 合并两个集合
2. 查询一个元素在哪个集合
3. 查询两个元素是否属于同一集合
一般用下面两个模板:
int get_par(int a) { if(par[a]!=a) par[a] = get_par(par[a]); return par[a]; } /* 也可以表示成这种形式,两种形式与数组的初始化有关。 int get_par(int a) { if(par[a]==-1) return a; return par[a] = get_par(par[a]); } */ int query(int a, int b) { return get_par(a)==get_par(b); } void merge(int a, int b) { par[get_par(a)]=get_par(b); }
《1》简单模板题。
http://poj.org/problem?id=1611

#include<iostream> using namespace std; const int MAXN = 30000; int n, m, k; int parent[MAXN+10]; int total[MAXN+10]; int GetParent(int a) { if(parent[a]!=a) parent[a]=GetParent(parent[a]); return parent[a]; } void Merge(int a, int b) { int p1 = GetParent(a); int p2 = GetParent(b); if(p1==p2) return; total[p1]+=total[p2]; parent[p2] = p1; } int main() { while(true) { scanf("%d%d", &n, &m); if(n==0&&m==0) break; for(int i=0; i<n; i++) parent[i] = i, total[i]=1; for(int i=0; i<m; ++i) { int h,s; scanf("%d", &k); scanf("%d", &h); for(int j=1; j<k; j++) { scanf("%d", &s); Merge(h, s); } } printf("%d ", total[GetParent(0)]); } return 0; }
《2》再来道裸题。
http://poj.org/problem?id=2524

#include<iostream> #include<cstring> using namespace std; const int MAXN = 50000 + 10; int Fa[MAXN]; int find(int x) { if(Fa[x]==-1) return x; return Fa[x]=find(Fa[x]); } void Merge(int u, int v) { int t1 = find(u); int t2 = find(v); if(t1!=t2) Fa[t1] = t2; } int main() { //freopen( "in.txt", "r", stdin ); //freopen( "out.txt", "w", stdout ); int n, m, u, v; int kase = 0; while(scanf("%d%d", &n, &m)==2) { if(n==0&&m==0) break; kase++; memset(Fa, -1, sizeof(Fa)); while(m--) { scanf("%d%d", &u, &v); Merge(u, v); } int ans = 0; for(int i=1; i<=n; i++) if(Fa[i]==-1) ans++; printf("Case %d: %d ", kase, ans); } return 0; }
《3》 这个题对小恪来说有点难, 但是收获不小哦!
http://poj.org/problem?id=1988
这道题很容易想到暴力的方法, 很不幸的是一定会超时。 嘿嘿!

#include<iostream> #include<cstdio> using namespace std; const int MAXN = 30000+10; int parent[MAXN]; int sum[MAXN]; //表示砖块 i 所在堆的砖块总数。 int under[MAXN];//under[i]表示砖块 i 下面有多少砖块。 int GetParent(int a) { if(parent[a]==a) return a; int t=GetParent(parent[a]); under[a]+=under[parent[a]]; parent[a] = t; return parent[a]; } //把 b 所在的堆,叠放到 a 所在的堆。 void Merge(int a, int b) { int n; int pa = GetParent(a); int pb = GetParent(b); if(pa==pb) return; parent[pb] = pa; under[pb] = sum[pa];//under[pb]在赋值前一定是 0 , //因为parent[pb] = pb, pb一定是最底下的。 sum[pa]+=sum[pb]; } int main() { int p; for(int i=0; i<MAXN; i++) { sum[i] = 1; under[i] = 0; parent[i] = i; } scanf("%d", &p); for(int i=0; i<p; i++) { char s[20]; int a, b; scanf("%s", s); if(s[0] == 'M') { scanf("%d%d", &a, &b); Merge(b, a); } else { scanf("%d", &a); GetParent(a); printf("%d ", under[a]); } } return 0; }
《4》这个题可以用纯粹的并查集解, 但是如何判定合并条件有点虐心!
http://acm.hdu.edu.cn/showproblem.php?pid=1198

#include<cstdio> const int MAXN = 100 + 10; char map[MAXN][MAXN]; int Fa[MAXN*MAXN]; int find(int x) { if(Fa[x]==-1) return x; return Fa[x] = find(Fa[x]); } void Merge(int a, int b) { int t1 = find(a); int t2 = find(b); if(t1!=t2) Fa[t1] = t2; } int main() { int m, n; while(scanf("%d%d", &m, &n)) { if(m<0||n<0) break; for(int i=0; i<m; i++) scanf("%s", &map[i]); for(int i=0; i<m*n; i++) Fa[i]= - 1; for(int i=0; i<m; i++) for(int j = 0; j<n; j++) { //判定条件有点虐心! 开口向: 上 下 左 右。 if(i>0&&(map[i][j]=='A'||map[i][j]=='B'||map[i][j]=='E'||map[i][j]=='G'||map[i][j]=='H'||map[i][j]=='J'||map[i][j]=='K')) if(map[i-1][j]=='C'||map[i-1][j]=='D'||map[i-1][j]=='E'||map[i-1][j]=='H'||map[i-1][j]=='I'||map[i-1][j]=='J'||map[i-1][j]=='K') Merge(i*n+j, (i-1)*n+j); if(j>0&&(map[i][j]=='A'||map[i][j]=='C'||map[i][j]=='F'||map[i][j]=='G'||map[i][j]=='H'||map[i][j]=='I'||map[i][j]=='K')) if(map[i][j-1]=='B'||map[i][j-1]=='D'||map[i][j-1]=='F'||map[i][j-1]=='G'||map[i][j-1]=='I'||map[i][j-1]=='J'||map[i][j-1]=='K') Merge(i*n+j,i*n+j-1); } int res = 0; for(int i=0; i<m*n; i++) if(Fa[i]==-1) res++; printf("%d ", res); } return 0; }
《5》赤裸裸的模板题
http://acm.hdu.edu.cn/showproblem.php?pid=1232

#include<cstdio> const int MAXN = 1000 + 10; int Fa[MAXN]; int find(int x) { if(Fa[x]==-1) return x; return Fa[x] = find(Fa[x]); } void Merge(int a, int b) { int t1 = find(a); int t2 = find(b); if(t1!=t2) Fa[t1] = t2; } int main() { int n, m; while(scanf("%d%d", &n, &m)!=EOF, n) { for(int i=1; i<=n; i++) Fa[i] = -1; int a, b; while(m--) { scanf("%d%d", &a, &b); Merge(a, b); } int res = 0; for(int i=1; i<=n; i++) if(Fa[i]==-1) res++; printf("%d ", res-1); } return 0; }
《6》来道难的吧(欧拉回路+字典树+并查集)
http://poj.org/problem?id=2513
此题好坑! 本想巧妙地绕过字典序, 然而题目中并未告诉你一共有那几种颜色(感觉这一点好贱!), 结果此题机智的卡死了我这等弱渣,弱渣是何等的悲哀啊! 先贴一下我的wong代码(适合各种颜色首字母都不同的情况, 此题数据中应有首字母相同的情况)

#include<cstdio> #include<cstring> #include<iostream> #include<string> #include<vector> using namespace std; const int maxn = 1000 + 5; int pa[256]; int findset(int x) { return pa[x] != x ? pa[x] = findset(pa[x]) : x; } int used[256], deg[256]; // 是否出现过;度数 int main() { int flag = 0; freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); char s1[105], s2[105]; memset(used, 0, sizeof(used)); memset(deg, 0, sizeof(deg)); for(int ch = 'a'; ch <= 'z'; ch++) pa[ch] = ch; // 初始化并查集 int cc = 26; // 连通块个数 while(scanf("%s%s", s1, s2)!=EOF) { flag = 1; char c1 = s1[0], c2 = s2[0]; deg[c1]++; deg[c2]--; used[c1] = used[c2] = 1; int s1 = findset(c1), s2 = findset(c2); if(s1 != s2) { pa[s1] = s2; cc--; } } vector<int> d; for(int ch = 'a'; ch <= 'z'; ch++) { if(!used[ch]) cc--; //没出现过的字母 else if(deg[ch] != 0) d.push_back(deg[ch]); } bool ok = false; if(cc == 1 && (d.empty() || (d.size() == 2 && (d[0] == 1 || d[0] == -1)))) ok = true; if(ok&&flag) printf("Possible "); else printf("Impossible "); return 0; }
下面是一巨巨的AC代码:

#include<stdio.h> #include<string.h> #include<iostream> #include<iostream> #include<algorithm> using namespace std; const int MAXN=500010; int F[MAXN]; const int MAX=26; int degree[MAXN];//度数 int color;//颜色编号,从0开始,最后就是颜色总数 int find(int x) { if(F[x]==-1)return x; return F[x]=find(F[x]); } void bing(int a,int b) { int t1=find(a); int t2=find(b); if(t1!=t2) F[t1]=t2; } typedef struct Trie_Node { bool isWord; struct Trie_Node *next[MAX]; int id; }Trie; int insert(Trie *root,char *word)//返回颜色编号 { Trie *p=root; int i=0; while(word[i]!='