并查集在查询连通关系上具有很大的作用,本身的代码很短,实现很容易,通过访问父节点直到父节点为本自身时即为访问到该点的祖先节点,使用f[x]=F(f[x])可以在查询一个点的祖先节点的同时,把路径上的所有点直接连接到祖先节点上,从而下次可以O(1)查询该点的祖先节点,在判断连通图连通与否等类似问题上具有很大的用处。
并查集可以在保存父节点f[]同时开另一个数组附加其他信息,如保存点到父节点的的距离,点与父节点的关系(同类与否,食物链关系),有附加信息的情况下,在使用F(x)来查询结点祖先结点的同时,要同时更新f[]数组和附加信息数组,以保证附加信息始终是结点对于父节点的信息。
二维平面上的并查集,预处理出点与点间的距离,每次有电脑修复就把与其距离小于等于d的点连通起来,查询只要祖先节点相同就是连通的了。
#include<iostream> #include<algorithm> #include<string.h> #include<stdio.h> #include<vector> #include<string> #include<cmath> #include<set> #include<queue> using namespace std; #define ll long long #define inf 0x3f3f3f3f const int mod = 1000000007; const int maxm = 1005; const int maxn = 1005; int n, m; pair<int, int> point[maxn]; int dis[maxn][maxn]; bool fix[maxn]; int f[maxn]; int F(int x){ if (f[x] == x)return x; return f[x] = F(f[x]); } int main() { int x, y; scanf("%d%d", &n, &m); for (int i = 0; i < n; i++){ f[i]=i; scanf("%d%d", &point[i].first, &point[i].second); } for (int i = 0; i < n; i++){ for (int j = i + 1; j < n; j++){ dis[j][i] = dis[i][j] = pow(abs(point[i].first - point[j].first), 2) + pow(abs(point[i].second - point[j].second), 2); } } char str[5]; while (~scanf("%s", str)){ if (str[0] == 'O'){ scanf("%d", &x); x--; fix[x] = 1; for (int i = 0; i < n; i++){ if (fix[i]&&dis[x][i]<=m*m){ f[F(i)] = F(x); } } } else{ scanf("%d%d", &x, &y); x--, y--; if (F(x) == F(y)){ printf("SUCCESS\n"); } else{ printf("FAIL\n"); } } } return 0; }比较基础的并查集题目,只要把同一个社团里的同学连通起来,最后与0号同学在同一个并查集里的人的数量就是答案。
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<set> using namespace std; typedef long long ll; const int maxn = 30005; const int mod = 1000000009; int n,m; int f[maxn]; int F(int x){ if (f[x] == x)return x; return f[x] = F(f[x]); } int main(){ int x,head,y; while (~scanf("%d%d", &n, &m), n + m){ for (int i = 0; i < n; i++)f[i] = i; for (int i = 0; i < m; i++){ scanf("%d", &x); if (x == 0)continue; scanf("%d", &head); for (int i = 1; i < x; i++){ scanf("%d", &y); f[F(y)] = F(head); } } int gg = F(0); int ans = 0; for (int i = 0; i < n; i++){ if (F(i) == gg){ ans++; } } printf("%d\n", ans); } return 0; }就是找连通块的数量。
#include<iostream> #include<algorithm> #include<string.h> #include<stdio.h> #include<vector> #include<string> #include<cmath> #include<set> #include<map> #include<queue> using namespace std; #define ll long long #define inf 0x3f3f3f3f const int mod = 1000000007; const int maxm = 50000005; const int maxn = 1005; const int M = 25; int n, m; int f[maxn]; int F(int x){ if (f[x] == x){ return x; } return f[x] = F(f[x]); } int main() { int t,x,y; scanf("%d", &t); while (t--){ scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++){ f[i] = i; } for (int i = 0; i < m; i++){ scanf("%d%d", &x, &y); f[F(x)] = F(y); } int ans = 0; for (int i = 1; i <= n; i++){ if (f[i] == i){ ans++; } } printf("%d\n", ans); } return 0; }这题并查集维护的东西是数轴上的点,在开f[]数组的同时还要开一个val[]数组保存点到父节点这段区间的数值之和,有递增的三个点abc,如果知道了ac区间的数值和以及ab直接的数值和,那么bc之间的数值和就可以得出来了,这就可以转换成并查集的模型了,如果两个点不在同一个并查集里,那么这两个点构成的区间的数值和也就还没有约束,否则就已经明确约束了该区间的值。
要注意维护并查集要同一以左端的结点为父节点或者右端的结点为父节点。
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<string.h> #include<set> using namespace std; typedef long long ll; const int maxn = 200005; const int mod = 1000000009; int n,m; int f[maxn]; int val[maxn]; int F(int x){ if (f[x] == x)return x; int tt = F(f[x]); val[x] += val[f[x]]; return f[x] = tt; } int main(){ int u, v, w; while (~scanf("%d%d", &n, &m)){ for (int i = 0; i <= n; i++)f[i] = i; memset(val, 0, sizeof(val)); int ans = 0; for (int i = 0; i < m; i++){ scanf("%d%d%d", &u, &v, &w); u--; int f1 = F(u); int f2 = F(v); if (f1 != f2){ f[f2] = f1; val[f2] = val[u] - val[v] + w; } else{ if (val[v] - val[u] != w)ans++; } } printf("%d\n", ans); } return 0; }很经典的题目,这题的并查集附加的信息是点与父节点的关系是(同类,吃,被吃),分别带入数值0,1,2,那么在mod3的情况下就可以直接用加减法更新附加信息(a吃b[1]+b吃c[1]=c被a吃[2]),运算上要注意。
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<string.h> #include<set> using namespace std; typedef long long ll; const int maxn = 50005; const int mod = 1000000009; int n, m; int f[maxn]; int relation[maxn]; int F(int x){ if (f[x] == x)return x; int tmp = F(f[x]); relation[x] = (relation[x] + relation[f[x]]) % 3; return f[x] = tmp; } int main(){ int op, x, y; scanf("%d%d", &n, &m); int ans = 0; memset(relation, 0, sizeof(relation)); for (int i = 1; i <= n; i++){ f[i] = i; } for (int i = 0; i < m; i++){ scanf("%d%d%d", &op, &x, &y); if (x>n || y > n){ ans++; continue; } if (op == 2 && x == y){ ans++; continue; } int f1 = F(x), f2 = F(y); if (op == 1){ if (f1 == f2){ if (relation[x] != relation[y]){ ans++; continue; } } else{ f[f1] = f2; relation[f1] = (-relation[x] + relation[y] + 3) % 3; } } else{//x吃y if (f1 == f2){ if (((relation[x] - relation[y] + 3) % 3) != 1){ ans++; continue; } } else{ f[f1] = f2; relation[f1] = (-relation[x] + relation[y] + 1 + 3) % 3; } } } printf("%d\n", ans); return 0; }十分综合的一道题目,并查集+背包DP+类似路径记录,对于任意a说b为yes的句子,ab就为同类,为no的句子,ab即为对立类,通过这样的关系可以构成一个个连通块,每一个连通块的的结点分为两类(正方&反方),两类与祖先节点的关系分别为同类和对立类(用附加信息数组维护),然后用dp对每一个并查集选择正方或者反方,求出正方为m人的方案数,若方案数为1,则能确定那些人为正方,哪些人为反方。
#include<iostream> #include<cstdio> #include<cmath> #include<string.h> using namespace std; const int maxn = 605; int n, m, k; struct pp{ int fa, relat; int same, other; int ttt; }f[maxn]; int dp[maxn][maxn]; int F(int x){ if (f[x].fa == x)return x; int tt = F(f[x].fa); f[x].relat = (f[x].relat + f[f[x].fa].relat) % 2; return f[x].fa = tt; } char str[10]; int head[maxn]; int main(){ int x, y, z; while (scanf("%d%d%d", &n,&m,&k), n+m+k){ memset(head, 0, sizeof(head)); for (int i = 1; i <= m+k; i++){ f[i].fa = i; f[i].other = f[i].relat = f[i].ttt = 0; f[i].same = 1; } bool ok = 0; int d; for (int i = 0; i < n; i++){ scanf("%d%d%s", &x, &y, str); if (ok)continue; int lf = F(x), rf = F(y); if (str[0] == 'y')d = 0; else d = 1; if (lf == rf && (f[x].relat + f[y].relat) % 2 != d)ok = 1; else if (lf != rf){ f[lf].fa = rf; f[lf].relat = (f[x].relat + f[y].relat + d) % 2; } } int tot = 1; if (!ok){ for (int i = 1; i <= m+k; i++){ int uu = F(i); if (uu == i)head[tot++]=i; else{ f[uu].other += f[i].relat; f[uu].same += 1 - f[i].relat; } } memset(dp, 0, sizeof(dp)); dp[1][f[head[1]].same] += 1; dp[1][f[head[1]].other] += 1; for (int i = 2; i < tot; i++){ int uu = head[i]; for (int j = 0; j <= m+k; j++){ if (dp[i - 1][j]){ dp[i][j + f[uu].same] += dp[i - 1][j]; dp[i][j + f[uu].other] += dp[i - 1][j]; } } } } if (dp[tot - 1][k] != 1 || ok)printf("no\n"); else{ int mm = m; for (int i = tot - 1; i > 0; i--){ int uu = head[i]; int vv = f[uu].same; if (i != 1 && dp[i - 1][mm - vv] !=0 || (i == 1 && mm == vv)){ f[uu].ttt = 1; mm -= vv; } else{ mm-= f[uu].other; } } for (int i = 1; i <= m+k; i++){ int uu = F(i); if (f[uu].ttt&&f[i].relat == 0 || f[uu].ttt == 0 && f[i].relat){ printf("%d\n", i); } } printf("end\n"); } } return 0; }这题我用优先队列逆着扫一遍实现的,并不是很懂怎么用并查集做。
#include<iostream> #include<cstdio> #include<string> #include<string.h> #include<algorithm> #include<queue> using namespace std; const int maxn = 10005; int n; int val[maxn]; struct pp{ int val, time; }p[maxn]; bool cmp(pp a, pp b){ return a.time > b.time; } int main(){ while (~scanf("%d", &n)){ for (int i = 0; i < n; i++){ scanf("%d%d", &p[i].val, &p[i].time); } int ans = 0; sort(p, p + n, cmp); priority_queue<int> que; int cnt = 0; for (int i = 10000; i >= 1; i--){ while (p[cnt].time >= i){ que.push(p[cnt++].val); } if (!que.empty()){ ans += que.top(); que.pop(); } } printf("%d\n", ans); } return 0; }跟那题区间数值和的很像,不过要离散化,离散化不能把不相邻的点离散到相邻,否则本来不在同一个连通块的就会在同一个连通块里了。因此,要嘛就是离散化之前把左端结点先-1(而不是离散化后再减)(注意给出的结点好像不一定是第一个小),要嘛离散化对于不同的点+2。
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<string.h> #include<set> using namespace std; typedef long long ll; const int maxn = 1000005; const int mod = 1000000009; int n, m; struct dd{ int num, id; bool l; }d[maxn]; int xx[maxn], yy[maxn], zz[maxn]; int f[maxn]; int ii[maxn]; int F(int x){ if (f[x] == x)return x; int tt = F(f[x]); ii[x] = (ii[x] + ii[f[x]]) % 2; return f[x] = tt; } char str[66]; bool cmp(dd a, dd b){ return a.num < b.num; } int main(){ scanf("%d", &n); for (int i = 0; i < maxn; i++){ f[i] = i; ii[i] = 0; } scanf("%d", &m); bool flag = 0; int ans = m; int tot = 0; for (int i = 0; i < m; i++){ scanf("%d%d%s", &d[tot].num, &d[tot + 1].num, str); if (d[tot].num>d[tot + 1].num)swap(d[tot].num, d[tot + 1].num); d[tot].num--; d[tot].id = d[tot + 1].id = i; d[tot].l = 1; d[tot + 1].l = 0; tot += 2; zz[i] = str[0] == 'e' ? 0 : 1; } sort(d, d + tot, cmp); int cnt = d[0].num; int ttt = 0; for (int i = 0; i < tot; i++){ if (d[i].num != cnt){ cnt = d[i].num; ttt += 1; } if (d[i].l){ xx[d[i].id] = ttt; } else{ yy[d[i].id] = ttt; } } for (int i = 0; i < m; i++){ int x = xx[i], y = yy[i], z = zz[i]; if (flag)continue; int f1 = F(x), f2 = F(y); if (f1 == f2){ if ((ii[y] - ii[x] + 2) % 2 != z){ ans = i; flag = 1; continue; } } else{ f[f2] = f1; ii[f2] = (ii[x] + z - ii[y] + 2) % 2; } } printf("%d", ans); return 0; }维护平面上的距离的并查集,模型很好理解,但是更新维护附加数组dis[]要十分小心。
#include<iostream> #include<string.h> #include<algorithm> #include<stdio.h> #include<cmath> #include<string> #include<stack> #include<vector> #define ll long long using namespace std; const int mod = 1000000007; const int maxm = 40005; const int maxn = 40005; const int maxk = 10005; const int inf = 0x3f3f3f3f; int n, m,k; int f[maxn]; int disx[maxn], disy[maxn]; int ans[maxk]; struct messg{ int u, v, l; char dir; }mes[maxm]; struct query{ int u, v, idx,id; bool operator<(const query b)const{ return idx < b.idx; } }id[maxk]; int F(int x){ if (f[x] == x)return x; int ff = f[x]; int cc = F(f[x]); disx[x] += disx[ff]; disy[x] += disy[ff]; return f[x] = cc; } int main(){ while (~scanf("%d%d", &n, &m)){ for (int i = 1; i <= n; i++){ f[i] = i; } memset(disx, 0, sizeof(disx)); memset(disy, 0, sizeof(disy)); for (int i = 0; i < m; i++){ scanf("%d%d%d%s", &mes[i].u, &mes[i].v, &mes[i].l, &mes[i].dir); } scanf("%d", &k); for (int i = 0; i < k; i++){ scanf("%d%d%d", &id[i].u, &id[i].v, &id[i].idx); id[i].id = i; } sort(id, id + k); int cnt = 0; for (int i = 0; i < k; i++){ while (cnt < id[i].idx){ messg cur = mes[cnt]; int f1 = F(cur.u); int f2 = F(cur.v); f[f1] = f2; disx[f1] = disx[cur.v]-disx[cur.u]; disy[f1] = disy[cur.v]-disy[cur.u]; if (cur.dir == 'N')disy[f1] += cur.l; else if (cur.dir == 'S')disy[f1] -= cur.l; else if (cur.dir == 'W')disx[f1] += cur.l; else disx[f1] -= cur.l; cnt++; } if (F(id[i].u) != F(id[i].v)){ ans[id[i].id] = -1; } else{ int a = abs(disx[id[i].u] - disx[id[i].v]); a += abs(disy[id[i].u] - disy[id[i].v]); ans[id[i].id] = a; } } for (int i = 0; i < k; i++){ printf("%d\n", ans[i]); } } return 0; }找有没有bug会和两种性别的bug接触,我用的二分图染色的方法做的,如果没有特殊的bug,得到的就会是一张二分图,不过用并查集的方法一样很好理解,维护附加数组,并查集里的任何三只虫必定有两只以上是同性别(不会接触),如果三只都互相接触过就存在一只特别的bug。
#include<stdio.h> #include<algorithm> #include<string.h> #include<iostream> #include<stack> #include<string> #include<vector> using namespace std; int n,m; bool ans; int color[2005]; vector<int> e[2005]; bool bipart(int cnt){ for(int i=0;i<e[cnt].size();i++){ int v=e[cnt][i]; if(!color[v]){ color[v]=3-color[cnt]; if(!bipart(v))return 0; } else if(color[v]==color[cnt]){return 0;} } return 1; } int main(){ int t,x,y; int cas=0; scanf("%d",&t); while(t--){ ans=1; memset(color,0,sizeof(color)); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){e[i].clear();} for(int i=0;i<m;i++){ scanf("%d%d",&x,&y); e[x].push_back(y); e[y].push_back(x); } for(int i=1;i<=n;i++){ if(!color[i]){ color[i]=1; if(!bipart(i)){ ans=0;break; } } } printf("Scenario #%d:\n",++cas); if(ans==0){printf("Suspicious bugs found!\n");} else {printf("No suspicious bugs found!\n");} if(t!=0){printf("\n");} } return 0; }模型跟食物链那题是一样的,有三个类别,我们要找出一个judge,他在剪刀石头布种会随机出,而其他玩家每个类别只会出固定的一种,枚举裁判,无视有裁判参与的回合,若有冲突则这个人有可能是裁判。
#include<iostream> #include<cstdio> #include<string> #include<string.h> #include<algorithm> #include<queue> #include<map> #include<set> using namespace std; const int maxn = 5005; int n,m,q; int f[maxn]; int vv[maxn]; int error[maxn]; struct kk{ int l, r,sta; }k[maxn]; int F(int x){ if (f[x] == x)return x; int tt = F(f[x]); vv[x] = (vv[x] + vv[f[x]]) % 3; return f[x] = tt; } void init(){ for (int i = 0; i < n; i++)f[i] = i; memset(vv, 0, sizeof(vv)); } int main(){ while (~scanf("%d%d", &n, &m)){ init(); for (int i = 0; i < m; i++){ scanf("%d", &k[i].l); char c; c = getchar(); scanf("%d", &k[i].r); k[i].sta = (c == '=' ? 0 : (c == '<' ? 2 : 1)); } memset(error, -1, sizeof(error)); for (int i = 0; i < n; i++){ init(); for (int j = 0; j < m; j++){ int aa = k[j].l, bb = k[j].r; if (i == aa || i ==bb)continue; int ra = F(aa), rb = F(bb); if (ra == rb){ if (k[j].sta == 0 && vv[aa] != vv[bb]){ error[i] = j + 1; break; } else if (k[j].sta == 2 && (vv[aa] - vv[bb] + 3) % 3 != 2){ error[i] = j + 1; break; } else if (k[j].sta == 1 && (vv[aa] - vv[bb] + 3) % 3 != 1){ error[i] = j + 1; break; } } else{ f[ra] = rb; vv[ra] = (-vv[aa]+k[j].sta+vv[bb]+3) % 3; } } } int cnt = 0, a1 = 0, a2 = 0; for (int i = 0; i < n; i++){ if (error[i] == -1){ cnt++; a1 = i; } a2 = max(a2, error[i]); } if (cnt == 0)puts("Impossible"); else if (cnt>1)puts("Can not determine"); else printf("Player %d can be determined to be the judge after %d lines\n", a1, a2); } return 0; }很好的一道题,并查集因为只能有连通的操作而不能有断开连通的操作,所以这题必须反向来做,其与的就是简单的处理f[]数组,围护并查集父节点要求为power值最大且结点序号最小的那一个。
#include<iostream> #include<cstdio> #include<string> #include<string.h> #include<algorithm> #include<queue> #include<map> #include<set> using namespace std; const int maxn = 10005; int n,m,q; int f[maxn]; int val[maxn]; set<pair<int, int> >s; pair<int, int> s1[2 * maxn]; pair<int, int> s2[5 * maxn]; char str[5 * maxn][10]; int F(int x){ if (f[x] == x)return x; return f[x] = F(f[x]); } int main(){ int x, y; int cas = 0; while (~scanf("%d", &n)){ if (cas){ printf("\n"); } else{ cas++; } s.clear(); for (int i = 0; i < n; i++){ f[i] = i; } for (int i = 0; i < n; i++){ scanf("%d", &val[i]); } scanf("%d", &m); for (int i = 0; i < m; i++){ scanf("%d%d", &s1[i].first,&s1[i].second); } scanf("%d", &q); for (int i = 0; i < q; i++){ scanf("%s", str[i]); if (str[i][0] == 'd'){ scanf("%d%d", &s2[i].first, &s2[i].second); s.insert({ s2[i].first, s2[i].second }); } else{ scanf("%d", &s2[i].first); } } for (int i = 0; i < m; i++){ int aa = s1[i].first, bb = s1[i].second; if (s.count({ aa,bb }) == 0 && s.count({ bb, aa }) == 0){ int l = F(aa), r = F(bb); int ml = val[l], mr = val[r]; if (ml>mr || (ml == mr&&l <= r)){ f[r] = l; } else{ f[l] = r; } } } vector<int > ans; for (int i = q - 1; i >= 0; i--){ if (str[i][0] == 'd'){ int l = F(s2[i].first), r = F(s2[i].second); int ml = val[F(l)], mr = val[F(r)]; if (ml>mr || (ml == mr&&l <= r)){ f[r] = l; } else{ f[l] = r; } } else{ int cur = s2[i].first; int vvv = val[F(cur)]; if (vvv > val[cur]){ ans.push_back(f[cur]); } else{ ans.push_back(-1); } } } int ttt = ans.size(); for (int i = ttt - 1; i >= 0; i--){ printf("%d\n", ans[i]); } } return 0; }图上的并查集,最为明显的模型了,连接结点是如果两个结点已经在同一个连通块种的话,那么迷宫就不成立了,并且还要要求只有一个连通块。
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<string.h> #include<set> using namespace std; typedef long long ll; const int maxn = 100005; const int mod = 1000000009; int n, m; int f[maxn]; bool vis[maxn]; int F(int x){ if (f[x] == x)return x; return f[x] = F(f[x]); } int x, y; int main(){ while (scanf("%d%d", &x, &y), x != -1){ if (x == 0 && y == 0){ printf("Yes\n"); continue; } memset(vis, 0, sizeof(vis)); for (int i = 1; i <= 100000; i++)f[i] = i; f[x] = y; vis[x] = vis[y] = 1; bool flag = 0; while (scanf("%d%d", &x, &y), x + y){ if (flag)continue; if (F(x) == F(y)){ flag = 1; continue; } vis[x] = vis[y] = 1; f[F(x)] = F(y); } int ff = -1; if (flag == 0){ for (int i = 1; i <= 100000; i++){ if (vis[i]){ if (ff == -1)ff = F(i); else{ if (F(i) != ff){ flag = 1; break; } } } } } if (flag)printf("No\n"); else printf("Yes\n"); } return 0; }跟上一个的模型很类似,不过边变成了有向边,求图是否为树,除了上一题的条件之外,对于每一个操作a连接一条有向边到b,b只能是叶子结点或者是一个连通块的祖先节点,否则肯定不是一棵树有向树。
#include<iostream> #include<cstdio> #include<string> #include<string.h> #include<algorithm> using namespace std; const int maxn = 100005; int f[maxn]; bool vis[maxn]; int F(int x){ if (f[x] == x)return x; return f[x] = F(f[x]); } int n, m; int main(){ int cas = 0; while (scanf("%d%d", &n, &m),n!=-1){ memset(vis, 0, sizeof(vis)); cas++; bool flag = 1; for (int i = 1; i < maxn; i++)f[i] = i; if (n + m == 0){ printf("Case %d is a tree.\n",cas); continue; } if (m == n)flag = 0; f[m] = n; vis[m] = vis[n] = 1; while (scanf("%d%d", &n, &m),n+m){ if (flag == 0)continue; int f1 = F(n), f2 = F(m); vis[n] = vis[m] = 1; if (f2 != m||f1==f2){ flag = 0; continue; } f[f2] = f1; } if (flag){ int ff = -1; for (int i = 1; i < maxn; i++){ if (vis[i]){ if (ff == -1)ff = F(i); else{ if (F(i) != ff){ flag = 0; break; } } } } } if (flag)printf("Case %d is a tree.\n", cas); else printf("Case %d is not a tree.\n", cas); } return 0; }要打省赛了,要好好加油啊。