kuangbin带我飞QAQ 并查集
1. POJ 2236
给出N个点,一开始图是空白的,两个操作,一个是增加一个点(给出坐标),一个是查询两个点间是否相通,当两点间的距离小于D或者两点通过其他点间接相连时说这两个点相通。并查集维护,每次增加一个点加入到vector中并于之前的点比较,距离小于D则合并到一个集合即可。
1 #include <iostream> 2 #include <string.h> 3 #include <cstdio> 4 #include <vector> 5 #include <string> 6 #include <algorithm> 7 #include <math.h> 8 #include <time.h> 9 10 #define SIGMA_SIZE 26 11 #define lson rt<<1 12 #define rson rt<<1|1 13 #pragma warning ( disable : 4996 ) 14 15 using namespace std; 16 typedef long long LL; 17 inline LL LMax(LL a,LL b) { return a>b?a:b; } 18 inline LL LMin(LL a,LL b) { return a>b?b:a; } 19 inline int Max(int a,int b) { return a>b?a:b; } 20 inline int Min(int a,int b) { return a>b?b:a; } 21 inline int gcd( int a, int b ) { return b==0?a:gcd(b,a%b); } 22 inline int lcm( int a, int b ) { return a/gcd(a,b)*b; } //a*b = gcd*lcm 23 const LL INF = 0x3f3f3f3f3f3f3f3f; 24 const LL mod = 1000000007; 25 const double eps = 1e-8; 26 const int inf = 0x3f3f3f3f; 27 const int maxk = 1005; 28 const int maxn = 1030; 29 30 int N, D; 31 vector<int> alive; 32 bool vis[maxk]; 33 int root[maxk]; 34 int xi[maxk], yi[maxk]; 35 36 37 int find( int x ) 38 { return x==root[x] ? x : root[x]=find(root[x]); } 39 40 void join( int x, int y ) 41 { 42 x = find(x); y = find(y); 43 if ( x == y ) 44 return; 45 root[x] = y; 46 } 47 48 int getDist( int i, int j ) 49 { return (xi[i]-xi[j])*(xi[i]-xi[j]) + (yi[i]-yi[j])*(yi[i]-yi[j]) ; } 50 51 void init() 52 { 53 alive.clear(); 54 memset(vis, 0, sizeof(vis) ); 55 56 for ( int i = 0; i < maxk; i++ ) 57 root[i] = i; 58 59 for ( int i = 1; i <= N; i++ ) 60 scanf( "%d %d", &xi[i], &yi[i] ); 61 } 62 63 int main() 64 { 65 freopen("F:\\cpp\\test.txt", "r", stdin ); 66 scanf("%d %d", &N, &D); 67 init(); 68 69 int x, y; 70 char str[5]; 71 while ( ~scanf("%s", str) ) 72 { 73 if ( str[0] == 'O' ) 74 { 75 scanf("%d", &x); 76 vis[x] = true; 77 alive.push_back(x); 78 79 for ( int i = 0; i < alive.size()-1; i++ ) 80 if ( getDist(alive[i], x) <= D*D ) 81 join(alive[i], x); 82 } 83 else 84 { 85 scanf("%d %d", &x, &y); 86 if ( (!vis[x]) || (!vis[y]) ) 87 printf("FAIL\n"); 88 else 89 { 90 x = find(x); y = find(y); 91 if ( x == y ) 92 printf("SUCCESS\n"); 93 else 94 printf("FAIL\n"); 95 } 96 } 97 } 98 }
2. HDU 1213
并查集裸题,给N个点和M个关系,问最终有多少个集合
1 #include <iostream> 2 #include <string.h> 3 #include <cstdio> 4 #include <vector> 5 #include <string> 6 #include <algorithm> 7 #include <math.h> 8 #include <time.h> 9 10 #define SIGMA_SIZE 26 11 #define lson rt<<1 12 #define rson rt<<1|1 13 #pragma warning ( disable : 4996 ) 14 15 using namespace std; 16 typedef long long LL; 17 inline LL LMax(LL a,LL b) { return a>b?a:b; } 18 inline LL LMin(LL a,LL b) { return a>b?b:a; } 19 inline int Max(int a,int b) { return a>b?a:b; } 20 inline int Min(int a,int b) { return a>b?b:a; } 21 inline int gcd( int a, int b ) { return b==0?a:gcd(b,a%b); } 22 inline int lcm( int a, int b ) { return a/gcd(a,b)*b; } //a*b = gcd*lcm 23 const LL INF = 0x3f3f3f3f3f3f3f3f; 24 const LL mod = 1000000007; 25 const double eps = 1e-8; 26 const int inf = 0x3f3f3f3f; 27 const int maxk = 1005; 28 const int maxn = 1030; 29 30 int N, M; 31 bool vis[maxk]; 32 int root[maxk]; 33 34 int find( int x ) 35 { return x==root[x] ? x : root[x]=find(root[x]); } 36 37 void join( int x, int y ) 38 { 39 x = find(x); y = find(y); 40 if ( x == y ) 41 return; 42 root[x] = y; 43 } 44 45 void init() 46 { 47 memset( vis, 0, sizeof(vis) ); 48 for ( int i = 0; i < maxk; i++ ) 49 root[i] = i; 50 } 51 52 int main() 53 { 54 //freopen("F:\\cpp\\test.txt", "r", stdin ); 55 56 int T; cin >> T; 57 while (T--) 58 { 59 int cnt = 0; 60 int x, y; 61 62 init(); 63 scanf("%d %d", &N, &M); 64 65 for ( int i = 1; i <= M; i++ ) 66 { 67 scanf("%d %d", &x, &y); 68 join(x,y); 69 } 70 71 for ( int i = 1; i <= N; i++ ) 72 vis[find(i)] = true; 73 for ( int i = 1; i <= N; i++ ) 74 if (vis[i]) 75 cnt++; 76 printf("%d\n", cnt); 77 } 78 79 return 0; 80 }
3.HDU 3038
和一道经典的题目(就是下一道)食物链很像,记得某场比赛做过类似的,当时以为是线段树各种做不出,,,结果发现原来是并查集
给一组数列,数字未知,给出A,B和[A,B]表示A到B这段子数列内的数字和,这样按顺序给出来,问你有多少个逻辑错误,遇到错误则跳过该错误。
比如给了[1,4] = 5, [1,2] = 3,这个时候再给一个[3,4]=6,那么这一行明显和前面得到的条件矛盾,所以错误数加一并且跳过这一行,具体是用向量操作,可以参考这两篇博客:
https://www.cnblogs.com/liyinggang/p/5327055.html
https://blog.csdn.net/niushuai666/article/details/6981689
1 #include <iostream> 2 #include <string.h> 3 #include <cstdio> 4 #include <vector> 5 #include <string> 6 #include <algorithm> 7 #include <math.h> 8 #include <time.h> 9 10 #define SIGMA_SIZE 26 11 #define lson rt<<1 12 #define rson rt<<1|1 13 #pragma warning ( disable : 4996 ) 14 15 using namespace std; 16 typedef long long LL; 17 inline LL LMax(LL a,LL b) { return a>b?a:b; } 18 inline LL LMin(LL a,LL b) { return a>b?b:a; } 19 inline int Max(int a,int b) { return a>b?a:b; } 20 inline int Min(int a,int b) { return a>b?b:a; } 21 inline int gcd( int a, int b ) { return b==0?a:gcd(b,a%b); } 22 inline int lcm( int a, int b ) { return a/gcd(a,b)*b; } //a*b = gcd*lcm 23 const LL INF = 0x3f3f3f3f3f3f3f3f; 24 const LL mod = 1000000007; 25 const double eps = 1e-8; 26 const int inf = 0x3f3f3f3f; 27 const int maxk = 1005; 28 const int maxn = 2e5+5; 29 30 int root[maxn]; 31 int sum[maxn]; 32 33 int _find( int x ) 34 { 35 int fa = root[x]; 36 37 if ( x == root[x] ) 38 return x; 39 40 root[x] = _find(fa); 41 sum[x] += sum[fa]; 42 43 return root[x]; 44 } 45 46 void init( int n ) 47 { 48 for ( int i = 0; i <= n; i++ ) 49 { 50 root[i] = i; 51 sum[i] = 0; 52 } 53 } 54 55 int main() 56 { 57 //freopen("F:\\cpp\\test.txt", "r", stdin ); 58 59 int N, M, cnt; 60 while (cin >> N >> M) 61 { 62 init(N); 63 64 cnt = 0; 65 int x, y, val; 66 int xr, yr; 67 while ( M-- ) 68 { 69 scanf("%d %d %d", &x, &y, &val); 70 71 x--; 72 xr = _find(x); yr = _find(y); 73 if ( xr == yr ) 74 { 75 if ( val != sum[x] - sum[y] ) 76 cnt++; 77 } 78 else 79 { 80 root[xr] = yr; 81 sum[xr] = sum[y]-sum[x]+val; 82 } 83 } 84 85 printf("%d\n", cnt); 86 } 87 88 return 0; 89 }
4. HDU 1272
POJ日常抽风,只能跑来做HDU的题目了233,实际上就是给定一个图让你判断是不是一颗连通树,所以有两个条件:无环,连通。无环用并查集判断,如果有两个点都在同一并查集,则说明肯定有环了。连通可以用(在无环的情况下)点数=边数+1判断,也可以用最后算出来总共只有一个集合来判断。坑爹的是要考虑空树,也就是直接给0 0的情况...
1 #include <iostream> 2 #include <string.h> 3 #include <cstdio> 4 #include <vector> 5 #include <set> 6 #include <string> 7 #include <algorithm> 8 #include <math.h> 9 #include <time.h> 10 11 #define SIGMA_SIZE 26 12 #define lson rt<<1 13 #define rson rt<<1|1 14 #pragma warning ( disable : 4996 ) 15 16 using namespace std; 17 typedef long long LL; 18 inline LL LMax(LL a,LL b) { return a>b?a:b; } 19 inline LL LMin(LL a,LL b) { return a>b?b:a; } 20 inline int Max(int a,int b) { return a>b?a:b; } 21 inline int Min(int a,int b) { return a>b?b:a; } 22 inline int gcd( int a, int b ) { return b==0?a:gcd(b,a%b); } 23 inline int lcm( int a, int b ) { return a/gcd(a,b)*b; } //a*b = gcd*lcm 24 const LL INF = 0x3f3f3f3f3f3f3f3f; 25 const LL mod = 1000000007; 26 const double eps = 1e-8; 27 const int inf = 0x3f3f3f3f; 28 const int maxk = 1005; 29 const int maxn = 1e5+5; 30 31 32 int root[maxn]; 33 set<int> num; 34 35 int _find( int x ) 36 { return x==root[x] ? x : root[x]=_find(root[x]); } 37 38 void merge( int x, int y ) 39 { 40 x = _find(x); y = _find(y); 41 if ( x == y ) 42 return; 43 root[x] = y; 44 } 45 46 void init() 47 { 48 num.clear(); 49 for ( int i = 0; i < maxn; i++ ) 50 root[i] = i; 51 } 52 53 int main() 54 { 55 //freopen("F:\\cpp\\test.txt", "r", stdin ); 56 57 int x, y; 58 while (1) 59 { 60 int cnt = 0; 61 bool ok = true; 62 init(); 63 64 scanf("%d %d", &x, &y); 65 if ( x == -1 ) 66 break; 67 if ( x == 0 ) 68 { 69 printf("Yes\n"); 70 continue; 71 } 72 merge(x,y); 73 num.insert(x); num.insert(y); 74 cnt++; 75 76 while (1) 77 { 78 scanf("%d %d", &x, &y); 79 if ( x == 0 ) 80 break; 81 num.insert(x); num.insert(y); 82 cnt++; 83 84 if (ok) 85 { 86 x = _find(x); y = _find(y); 87 if ( x == y ) 88 ok = false; 89 90 merge(x, y); 91 } 92 } 93 94 if ( ok && num.size() == cnt+1 ) 95 printf("Yes\n"); 96 else 97 printf("No\n"); 98 } 99 return 0; 100 }
5. ZOJ 3261
思路简单,写起来麻烦...大意是给N个点M条边,每个点有一个power值。然后有两个操作,一是毁灭其中一条边,二是询问一个点A,求与A直接或间接相连的所有点中power值最大的那个,且求出来的power值需大于A的power值,当有多个点power值相同,取序号最小那个。
显然并查集不支持删除,所以需要逆向操作,先按顺序存储所有操作,做出删完所有该删的边的最终图,再从后往前加边,询问。
询问操作比较好存,毁灭操作就难一点了,这里我用了map,因为map搜索是log级别的,即使要搜两边(x y和y x)也很快。其他除了并查集merge的时候要注意点就没什么了,就是写的好慢...
1 #include <iostream> 2 #include <string.h> 3 #include <cstdio> 4 #include <vector> 5 #include <map> 6 #include <string> 7 #include <algorithm> 8 #include <math.h> 9 #include <time.h> 10 11 #define SIGMA_SIZE 26 12 #define lson rt<<1 13 #define rson rt<<1|1 14 #pragma warning ( disable : 4996 ) 15 16 using namespace std; 17 typedef long long LL; 18 inline LL LMax(LL a,LL b) { return a>b?a:b; } 19 inline LL LMin(LL a,LL b) { return a>b?b:a; } 20 inline int Max(int a,int b) { return a>b?a:b; } 21 inline int Min(int a,int b) { return a>b?b:a; } 22 inline int gcd( int a, int b ) { return b==0?a:gcd(b,a%b); } 23 inline int lcm( int a, int b ) { return a/gcd(a,b)*b; } //a*b = gcd*lcm 24 const LL INF = 0x3f3f3f3f3f3f3f3f; 25 const LL mod = 1000000007; 26 const double eps = 1e-8; 27 const int inf = 0x3f3f3f3f; 28 const int maxk = 1005; 29 const int maxn = 1e4+5; 30 31 //struct edge { 32 // int to, next; 33 //}e[4*maxn]; 34 35 map<pair<int,int>,bool> mmap; 36 map<pair<int,int>,bool>::iterator itor; 37 38 int xi[5*maxn], yi[5*maxn], query[5*maxn]; 39 int root[maxn], val[maxn]; 40 int N, M, Q, death_num; 41 42 43 //void addedge( int u, int v ) 44 //{ e[cnt].to = v; e[cnt].next = linjie[u]; linjie[u] = cnt++; } 45 46 int _find( int x ) 47 { return x==root[x] ? x : root[x]=_find(root[x]); } 48 49 void merge( int x, int y ) 50 { 51 x = _find(x); y = _find(y); 52 if ( x == y ) 53 return; 54 55 //让 x < y 56 if ( x > y ) swap(x,y); 57 58 if ( val[x] > val[y] ) root[y] = x; 59 else if ( val[x] < val[y] ) root[x] = y; 60 else root[y] = x; 61 } 62 63 void init( int n ) 64 { 65 death_num = 0; 66 mmap.clear(); 67 68 //memset( linjie, -1, sizeof(linjie) ); 69 for ( int i = 0; i <= n; i++ ) 70 root[i] = i; 71 } 72 73 void read( int n ) 74 { 75 //读入power 76 for ( int i = 0; i < n; i++ ) 77 scanf("%d", &val[i]); 78 79 //读入边 80 cin >> M; 81 int x, y; 82 for ( int i = 0; i < M; i++ ) 83 { 84 scanf("%d %d", &x, &y); 85 mmap.insert( make_pair(make_pair(x,y), true) ); 86 } 87 88 //读入query 89 cin >> Q; 90 char str[10]; 91 for ( int i = 0; i < Q; i++ ) 92 { 93 scanf("%s", str); 94 if ( str[0] == 'q' ) 95 { 96 scanf("%d", &x); 97 query[i] = x; 98 } 99 else 100 { 101 //依次读入被毁灭的边,然后在mmap中寻找并变为false 102 scanf("%d %d", &xi[death_num], &yi[death_num]); 103 itor = mmap.find(make_pair(xi[death_num],yi[death_num])); 104 if ( itor != mmap.end() ) 105 (*itor).second = false; 106 else 107 { 108 itor = mmap.find(make_pair(yi[death_num],xi[death_num])); 109 (*itor).second = false; 110 } 111 112 death_num++; 113 query[i] = -2; 114 } 115 } 116 } 117 118 void make_map() 119 { 120 int x, y; 121 122 //如果该边没有被毁灭 123 for ( itor = mmap.begin(); itor != mmap.end(); itor++ ) 124 if ( (*itor).second ) 125 { 126 x = ((*itor).first).first; y = ((*itor).first).second; 127 //addedge( x, y ); 128 //addedge( y, x ); 129 merge( x, y ); 130 } 131 } 132 133 void solve() 134 { 135 int tmp; 136 for ( int i = Q-1; i >= 0; i-- ) 137 { 138 139 if ( query[i] != -2 ) //如果是询问 140 { 141 tmp = _find(query[i]); 142 if ( val[tmp] <= val[query[i]] ) 143 query[i] = -1; 144 else 145 query[i] = tmp; 146 } 147 else 148 { 149 death_num--; 150 merge(xi[death_num], yi[death_num]); 151 } 152 } 153 } 154 155 int main() 156 { 157 //freopen("F:\\cpp\\test.txt", "r", stdin ); 158 159 int cnt = 0; 160 while ( ~scanf("%d", &N) ) 161 { 162 if ( cnt ) 163 printf("\n"); 164 init(N); 165 read(N); 166 make_map(); 167 solve(); 168 for ( int i = 0; i < Q; i++ ) 169 if ( query[i] != -2) 170 printf("%d\n", query[i]); 171 cnt++; 172 } 173 174 return 0; 175 }
6. POJ 2492
并查集做这种题实在太好了,这道题像是食物链的弱化版,给N个虫子,只有异性间才会交配.....给M个交配方案,看看虫子里面有没有同性恋(笑)这个关系还是比较简单的,用0表示两虫子同性,1表示虫子异性,rela[i]表示i和根节点的关系。
三个虫子中有三种关系(A-B, B-C, C-A)只要知道其中两种,很容易推出第三种,然后推出的关系维护并查集,具体看代码,很简单
1 #include <iostream> 2 #include <string.h> 3 #include <cstdio> 4 #include <vector> 5 #include <map> 6 #include <string> 7 #include <algorithm> 8 #include <math.h> 9 #include <time.h> 10 11 #define SIGMA_SIZE 26 12 #define lson rt<<1 13 #define rson rt<<1|1 14 #pragma warning ( disable : 4996 ) 15 16 using namespace std; 17 typedef long long LL; 18 inline LL LMax(LL a,LL b) { return a>b?a:b; } 19 inline LL LMin(LL a,LL b) { return a>b?b:a; } 20 inline int Max(int a,int b) { return a>b?a:b; } 21 inline int Min(int a,int b) { return a>b?b:a; } 22 inline int gcd( int a, int b ) { return b==0?a:gcd(b,a%b); } 23 inline int lcm( int a, int b ) { return a/gcd(a,b)*b; } //a*b = gcd*lcm 24 const LL INF = 0x3f3f3f3f3f3f3f3f; 25 const LL mod = 1000000007; 26 const double eps = 1e-8; 27 const int inf = 0x3f3f3f3f; 28 const int maxk = 1e6+5; 29 const int maxn = 2005; 30 31 //rela[i]表示i与其根的关系 为0表示同性,为1表示异性 32 int root[maxn]; 33 bool rela[maxn]; 34 int N, Q; 35 36 void init() 37 { 38 scanf("%d %d", &N, &Q); 39 40 memset( rela, 0, sizeof(rela) ); 41 for ( int i = 0; i <= N; i++ ) 42 root[i] = i; 43 } 44 45 bool getRela( bool master, bool minor ) 46 { 47 //如果两者关系为异性,则第三条关系相反 48 if ( master ) 49 return (!minor); 50 else 51 return minor; 52 } 53 54 int _find( int x ) 55 { 56 int r = x, pre = root[x]; 57 if ( r == root[r] ) 58 return root[r]; 59 60 r = _find(root[r]); 61 root[x] = r; 62 rela[x] = getRela( rela[x], rela[pre] ); 63 64 return root[x]; 65 } 66 67 void merge( int x, int y, int xr, int yr ) 68 { 69 //tmp是y和xr的关系 70 bool tmprela = getRela( true, rela[x] ); 71 root[xr] = yr; 72 rela[xr] = getRela( tmprela, rela[y] ); 73 } 74 75 int main() 76 { 77 //freopen("F:\\cpp\\test.txt", "r", stdin ); 78 79 int T; cin >> T; 80 int CNT = 1; 81 while (T--) 82 { 83 bool nobug = true; 84 int x, y, xr, yr; 85 86 init(); 87 while (Q--) 88 { 89 scanf("%d %d", &x, &y); 90 if (nobug) 91 { 92 xr = _find(x); yr = _find(y); 93 if ( xr == yr ) 94 { 95 if ( rela[x] != getRela(true, rela[y]) ) 96 nobug = false; 97 } 98 else 99 merge(x, y, xr, yr); 100 } 101 } 102 printf("Scenario #%d:\n", CNT++); 103 if ( nobug ) 104 printf("No suspicious bugs found!\n"); 105 else 106 printf("Suspicious bugs found!\n"); 107 printf("\n"); 108 } 109 110 return 0; 111 }
7.POJ 1182
食物链,经典题目,有了前面的铺垫这道题就算是硬算也不难,前面几题集合中只有两种关系,这里就有三种关系,本质上是向量+偏移量,懂了还是不难的
PS:这道题只能单次输入,如果加了while循环输入就会WA...完全不知道为什么,害我查了一个小时的bug......
1 #include <iostream> 2 #include <string.h> 3 #include <cstdio> 4 #include <vector> 5 #include <map> 6 #include <string> 7 #include <algorithm> 8 #include <math.h> 9 #include <time.h> 10 11 #define SIGMA_SIZE 26 12 #define lson rt<<1 13 #define rson rt<<1|1 14 #pragma warning ( disable : 4996 ) 15 16 using namespace std; 17 typedef long long LL; 18 inline LL LMax(LL a,LL b) { return a>b?a:b; } 19 inline LL LMin(LL a,LL b) { return a>b?b:a; } 20 inline int Max(int a,int b) { return a>b?a:b; } 21 inline int Min(int a,int b) { return a>b?b:a; } 22 inline int gcd( int a, int b ) { return b==0?a:gcd(b,a%b); } 23 inline int lcm( int a, int b ) { return a/gcd(a,b)*b; } //a*b = gcd*lcm 24 const LL INF = 0x3f3f3f3f3f3f3f3f; 25 const LL mod = 1000000007; 26 const double eps = 1e-8; 27 const int inf = 0x3f3f3f3f; 28 const int maxk = 1e6+5; 29 const int maxn = 5e4+5; 30 31 int root[maxn], rela[maxn]; 32 33 int _find( int x ) 34 { 35 if ( x == root[x] ) 36 return x; 37 38 int pre = root[x]; 39 root[x] = _find(root[x]); 40 rela[x] = (rela[x] + rela[pre]) % 3; 41 42 return root[x]; 43 } 44 45 int main() 46 { 47 //freopen("F:\\cpp\\test.txt", "r", stdin ); 48 int N, K, cnt; 49 scanf("%d %d", &N, &K); 50 for ( int i = 0; i <= N; i++ ) 51 root[i] = i; 52 cnt = 0; 53 memset( rela, 0, sizeof(rela) ); 54 55 56 int d, x, y, xr, yr; 57 while (K--) 58 { 59 scanf("%d %d %d", &d, &x, &y); 60 d--; 61 62 if ( x > N || y > N ) 63 { cnt++; continue; } 64 if ( x == y && d == 1 ) 65 { cnt++; continue; } 66 67 xr = _find(x); yr = _find(y); 68 if ( xr == yr ) 69 { 70 if ( d != ((rela[x] - rela[y] + 3)%3) ) 71 cnt++; 72 } 73 else 74 { 75 root[xr] = yr; 76 rela[xr] = (rela[y] + d - rela[x] + 3) % 3; 77 } 78 } 79 printf("%d\n", cnt); 80 81 return 0; 82 }
8.POJ 1611 The Suspects
并查集裸题,给M个组,每个组有N个学生,其中0号学生是感染者,和感染者同一组的都是感染者,所以只要求0号学生为根的集合的学生个数即可,merge时约定0号学生为根就可以了。
1 #include <iostream> 2 #include <string.h> 3 #include <cstdio> 4 #include <vector> 5 #include <map> 6 #include <string> 7 #include <algorithm> 8 #include <math.h> 9 #include <time.h> 10 11 #define SIGMA_SIZE 26 12 #define lson rt<<1 13 #define rson rt<<1|1 14 #pragma warning ( disable : 4996 ) 15 16 using namespace std; 17 typedef long long LL; 18 inline LL LMax(LL a,LL b) { return a>b?a:b; } 19 inline LL LMin(LL a,LL b) { return a>b?b:a; } 20 inline int Max(int a,int b) { return a>b?a:b; } 21 inline int Min(int a,int b) { return a>b?b:a; } 22 inline int gcd( int a, int b ) { return b==0?a:gcd(b,a%b); } 23 inline int lcm( int a, int b ) { return a/gcd(a,b)*b; } //a*b = gcd*lcm 24 const LL INF = 0x3f3f3f3f3f3f3f3f; 25 const LL mod = 1000000007; 26 const double eps = 1e-8; 27 const int inf = 0x3f3f3f3f; 28 const int maxk = 3e4+5; 29 const int maxn = 5e4+5; 30 31 int root[maxk], num[maxk]; 32 int N, M; 33 34 void init() 35 { 36 for ( int i = 0; i <= N; i++ ) 37 { 38 root[i] = i; 39 num[i] = 1; 40 } 41 } 42 43 int _find(int x) 44 { return x==root[x] ? x : root[x] = _find(root[x]); } 45 46 void merge( int x, int y ) 47 { 48 int xr = _find(x); 49 int yr = _find(y); 50 51 if (xr != yr) 52 { 53 if (xr == 0) 54 { 55 root[yr] = xr; 56 num[xr] = num[xr] + num[yr]; 57 } 58 else 59 { 60 root[xr] = yr; 61 num[yr] = num[xr]+num[yr]; 62 } 63 } 64 } 65 66 int main() 67 { 68 while ( ~scanf("%d %d", &N, &M) ) 69 { 70 if ( N == 0 && M == 0 ) 71 break; 72 73 init(); 74 75 int x; 76 for ( int j = 0; j < M; j++ ) 77 { 78 int a, b; 79 80 scanf("%d", &x); 81 scanf("%d", &a); 82 if ( x != 1 ) 83 for (int i = 1; i <= x-1; i++) 84 { 85 scanf("%d", &b); 86 merge(a, b); 87 a = b; 88 89 } 90 } 91 92 printf("%d\n", num[0]); 93 } 94 95 return 0; 96 }
9. POJ 1417 True Liars
村子里面有p1个好人p2个坏人,好人只会说真话,坏人只会说假话,现在可以问N个问题, 每个问题就是随机挑两人a和b,让a回答b是不是好人,然后让你判断出所有的好人。
如果这道题只是求有没有矛盾,那就是一道很简单的带权并查集,和前面那个同性恋虫子思路一样。但现在问题是要求出所有好人,显然只根据问的N个问题是求不出来的,因为如果没有第一个能被确定为好人或者坏人的人,那么剩下所有人都是一个相对的关系,这样只能推断两个人是同为好人还是同为坏人或者一好一坏。经过简单的推算发现回答为yes时a和b为同类人,no时a与b异类,开个rela[i]数组表示i与根节点是同类还是异类, 这样带权并查集直接走起。
那么我们可以用并查集先求出几个大集合,并且记录每个大集合A类人和B类人的个数,当然我们现在并不知道A类是好人还是坏人,只是一个相对的关系。显而易见我们应该在每个大集合各中抽取一个A类人或者B类人组成好人集合,如果好人集合人数正好为p1,并且这种选法唯一那我们就能确定所有好人了。比如只有一个大集合有4个A类人和4个B类人,而好人只有4个,那我们就不知道到底是A类是好人还是B类是好人了。实际上就像一个组合问题,看能否形成唯一的组合使其总数正好为p1,这点用dp很简单就能实现,dp[i][j]表示第i个大集合中使人数为j的方法最多有多少种,最后只要看dp[cnt][p1]是否等于1就可以了。
然而这题最麻烦的还是记录路径,有一种方法是如果从dp[cnt][p1]往前推。因为dp[i][j]必然是由dp[i-1][j-A]和dp[i-1][j-B](A, B分别为某个大集合A类人和B类人的个数)推过来的,这两个dp只有一个为1,不然dp[i][j]就等于2了(有两种路径可以使好人人数达到 j ),所以只要将p1一步一步减回去看dp为1的那一类就是好人类。
1 #include <iostream> 2 #include <string.h> 3 #include <cstdio> 4 #include <vector> 5 #include <map> 6 #include <string> 7 #include <algorithm> 8 #include <time.h> 9 10 #define SIGMA_SIZE 26 11 #define lson rt<<1 12 #define rson rt<<1|1 13 #pragma warning ( disable : 4996 ) 14 15 using namespace std; 16 typedef long long LL; 17 inline LL LMax(LL a,LL b) { return a>b?a:b; } 18 inline LL LMin(LL a,LL b) { return a>b?b:a; } 19 inline int Max(int a,int b) { return a>b?a:b; } 20 inline int Min(int a,int b) { return a>b?b:a; } 21 inline int gcd( int a, int b ) { return b==0?a:gcd(b,a%b); } 22 inline int lcm( int a, int b ) { return a/gcd(a,b)*b; } //a*b = gcd*lcm 23 const LL INF = 0x3f3f3f3f3f3f3f3f; 24 const LL mod = 1000000007; 25 const double eps = 1e-8; 26 const int inf = 0x3f3f3f3f; 27 const int maxk = 3e4+5; 28 const int maxn = 600; 29 30 31 int n, p1, p2, cnt; 32 //num[i][0]表示该集合中与根节点i种类相同的人个数,反正亦然 33 int num[602][2], root[602]; 34 //dp[i][j]表示前i的个大集合中天使人数为j的集合最多多少个 35 int dp[602][602]; 36 int divide[602][2]; 37 bool rela[602]; //false表示与根为同类 38 39 vector<int> ans, _list; 40 vector<int> v0[602], v1[602]; 41 42 void init() 43 { 44 cnt = 1; ans.clear(); _list.clear(); 45 memset(num, 0, sizeof(num)); 46 memset(rela, 0, sizeof(rela)); 47 memset(dp, 0, sizeof(dp)); 48 for ( int i = 0; i <= p1+p2; i++ ) 49 { 50 root[i] = i; 51 num[i][0] = 1; 52 } 53 } 54 55 int _find( int x ) 56 { 57 if (x == root[x]) 58 return root[x]; 59 60 int pre = _find(root[x]); 61 62 if (rela[root[x]]) 63 rela[x] = !rela[x]; 64 root[x] = pre; 65 66 return pre; 67 } 68 69 void merge( int x, int y, bool link ) 70 { 71 int xr = _find(x), yr = _find(y); 72 if (xr == yr) 73 return; 74 75 if (rela[x]) 76 link = !link; 77 if (rela[y]) 78 rela[xr] = !link; 79 else 80 rela[xr] = link; 81 82 root[xr] = yr; 83 84 //如果xr与yr是同类,则yr组中同类的数目要加上xr组中与xr同类的个数 85 if (!rela[xr]) 86 { 87 num[yr][0] += num[xr][0]; 88 num[yr][1] += num[xr][1]; 89 } 90 else 91 { 92 num[yr][0] += num[xr][1]; 93 num[yr][1] += num[xr][0]; 94 } 95 } 96 97 void solve() 98 { 99 _list.push_back(0); 100 for ( int i = 1; i <= p1+p2; i++ ) 101 { 102 if ( _find(i) == i ) 103 { 104 divide[cnt][0] = num[i][0]; 105 divide[cnt][1] = num[i][1]; 106 _list.push_back(i); 107 cnt++; 108 } 109 } 110 111 for ( int i = 1; i <= p1+p2; i++ ) 112 { 113 int rt = root[i]; 114 if ( !rela[i] ) 115 v0[rt].push_back(i); 116 else 117 v1[rt].push_back(i); 118 } 119 120 //处理DP 121 dp[0][0] = 1; 122 for (int i = 1; i < cnt; i++) 123 { 124 int mmin = Min(divide[i][0], divide[i][1]); 125 for (int j = p1; j >= mmin; j--) 126 { 127 dp[i][j] += dp[i-1][j-divide[i][0]]; 128 dp[i][j] += dp[i-1][j-divide[i][1]]; 129 } 130 } 131 132 133 if ( dp[cnt-1][p1] == 1 ) 134 { 135 int tmp1 = p1, tmp2 = p2; 136 for ( int i = cnt-1; i >= 1; i-- ) 137 { 138 if ( tmp1-divide[i][0]>=0 && tmp2-divide[i][1]>=0 && dp[i-1][tmp1-divide[i][0]]==1 ) 139 { 140 int size = v0[_list[i]].size(); 141 for ( int j = 0; j < size; j++ ) 142 ans.push_back(v0[_list[i]][j]); 143 tmp1 -= divide[i][0]; 144 tmp2 -= divide[i][1]; 145 } 146 else if ( tmp1-divide[i][1]>=0 && tmp2-divide[i][0]>=0 && dp[i-1][tmp1-divide[i][1]]==1 ) 147 { 148 int size = v1[_list[i]].size(); 149 for ( int j = 0; j < size; j++ ) 150 ans.push_back(v1[_list[i]][j]); 151 tmp1 -= divide[i][1]; 152 tmp2 -= divide[i][0]; 153 } 154 155 v0[_list[i]].clear(); 156 v1[_list[i]].clear(); 157 } 158 sort(ans.begin(), ans.end()); 159 for ( int i = 0; i < ans.size(); i++ ) 160 printf("%d\n", ans[i]); 161 printf("end\n"); 162 } 163 else 164 { 165 printf("no\n"); 166 for ( int i = 1; i < _list.size(); i++ ) 167 { 168 v0[_list[i]].clear(); 169 v1[_list[i]].clear(); 170 } 171 } 172 } 173 174 int main() 175 { 176 while ( ~scanf("%d %d %d", &n, &p1, &p2) && n+p1+p2 ) 177 { 178 init(); 179 int x, y; 180 char str[5]; 181 bool link = false; //false表示x与y是同类 182 183 while (n--) 184 { 185 scanf("%d %d", &x, &y); scanf("%s", str); 186 if (str[0] == 'n') link = true; 187 else link = false; 188 189 merge(x, y, link); 190 } 191 192 solve(); 193 } 194 195 return 0; 196 }
10. POJ 1733
看上去是一道简单的种类并查集,但是关系并不好找,在网上看到一大神的思路,遂跪。
对于[l,r],我们可以简单的设sum[l-1],sum[r]为0~l-1,0~r的前缀,这和前面种类并查集是一样的,然而这道题只给了你[l,r]之间1的个数为奇数或者偶数,这我半天都没反应过来怎么转换。其实可以这样想,如果[l,r]之间1个数为奇数,那么sum[l-1]^sum[r]就应该等于1,因为可以把sum[r]拆为0~l-1和l~r这两端,前面一段和sum[l-1]异或就变成零了,后面一段因为有奇数个1所以异或结果为1,同理如果[l,r]间1为偶数,那么最后就应该等于0。但这道题我们并不需要存sum,我们只要一个rela数组存奇偶关系, 所以每条向量(假设向量两个端点为x,y)的rela值就应该等于sum[x]^sum[y]。令三角向量三个端点为l-1, r, root,稍微推一下就知道怎么写了。
1 #include <iostream> 2 #include <string.h> 3 #include <cstdio> 4 #include <vector> 5 #include <map> 6 #include <string> 7 #include <algorithm> 8 #include <time.h> 9 10 #define SIGMA_SIZE 26 11 #define lson rt<<1 12 #define rson rt<<1|1 13 #pragma warning ( disable : 4996 ) 14 15 using namespace std; 16 typedef long long LL; 17 inline LL LMax(LL a,LL b) { return a>b?a:b; } 18 inline LL LMin(LL a,LL b) { return a>b?b:a; } 19 inline int Max(int a,int b) { return a>b?a:b; } 20 inline int Min(int a,int b) { return a>b?b:a; } 21 inline int gcd( int a, int b ) { return b==0?a:gcd(b,a%b); } 22 inline int lcm( int a, int b ) { return a/gcd(a,b)*b; } //a*b = gcd*lcm 23 const LL INF = 0x3f3f3f3f3f3f3f3f; 24 const LL mod = 1000000007; 25 const double eps = 1e-8; 26 const int inf = 0x3f3f3f3f; 27 const int maxn = 2e4+5; 28 29 30 //1个数为偶数则为0 31 int rela[maxn]; 32 int root[maxn], num[maxn]; 33 int len, N, cnt, n; 34 35 struct node { 36 int lhs, rhs, rela; 37 }lisan[maxn]; 38 39 int _find(int x) 40 { 41 if ( x == root[x] ) return root[x]; 42 int pre = _find(root[x]); 43 rela[x] = rela[x]^rela[root[x]]; 44 root[x] = pre; 45 return root[x]; 46 } 47 48 void init() 49 { 50 cnt = 0; 51 memset(rela, 0, sizeof(rela)); 52 } 53 54 void read() 55 { 56 scanf("%d", &N); 57 init(); 58 59 int x, y; char str[5]; 60 for (int i = 1; i <= N; i++ ) 61 { 62 scanf("%d %d %s", &lisan[i].lhs, &lisan[i].rhs, str ); 63 lisan[i].rela = str[0]=='e' ? 0 : 1; 64 num[++cnt] = lisan[i].lhs-1; num[++cnt] = lisan[i].rhs; 65 } 66 67 sort(num+1, num+1+cnt); 68 n = unique(num+1, num+1+cnt)-num-1; 69 } 70 71 int main() 72 { 73 while (~scanf("%d", &len)) 74 { 75 read(); 76 77 for ( int i = 0; i <= n; i++ ) 78 root[i] = i; 79 80 int x, y, xr, yr; 81 for ( int i = 1; i <= N; i++ ) 82 { 83 x = lower_bound(num+1, num+1+n, lisan[i].lhs-1)-num; 84 y = lower_bound(num+1, num+1+n, lisan[i].rhs)-num; 85 86 xr = _find(x); yr = _find(y); 87 if ( xr == yr ) 88 { 89 if ( rela[x]^rela[y] != lisan[i].rela ) 90 { printf("%d\n", i-1); return 0; } 91 } 92 else 93 { 94 root[xr] = yr; 95 rela[xr] = rela[x]^rela[y]^lisan[i].rela; 96 } 97 } 98 printf("%d", N); 99 } 100 101 return 0; 102 }