[kuangbin带我飞]专题五--并查集 (9/14)

1、

Wireless Network

 POJ - 2236

 

题意:有n台电脑,距离d以内的两台电脑维修后可以互相联系。一台电脑可以通过一对能够互相联系的电脑中的一个与另一个联系。给出每台电脑的坐标,再给出指令,维修一台电脑或是查询某两台电脑是否能够相互联系。对于每个查询指令输出答案。

思路:很普通的并查集。

代码:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <cmath>
 4 using namespace std;
 5 const int N = 1010;
 6 struct node{
 7     double x, y;
 8 }c[N];
 9 int n, d, f[N], g[N];
10 bool vis[N];
11 
12 int find(int x);
13 void merge(int x, int y);
14 bool check(node a, node b);
15 void init();
16 
17 int main()
18 {
19     scanf("%d%d", &n, &d);
20     init();
21     for (int i=1;i<=n;i++)
22     {
23         scanf("%lf%lf", &c[i].x, &c[i].y);
24     }
25     char op;
26     int num, cnt = 0, x, y;
27     while (~scanf("%c", &op))
28     {
29         if (op == 'O')
30         {
31             scanf("%d", &num);
32             if (vis[num] == false)
33             {
34                 for (int i=1;i<=cnt;i++)
35                 {
36                     if (check(c[g[i]], c[num]))
37                         merge(g[i], num);
38                 }
39                 cnt++;
40                 g[cnt] = num;
41                 vis[num] = true;
42             }
43         }
44         else if (op == 'S')
45         {
46             scanf("%d %d", &x, &y);
47             if (find(x) == find(y))
48                 puts("SUCCESS");
49             else
50                 puts("FAIL");
51         }
52     }
53     return 0;
54 }
55 
56 int find(int x)
57 {
58     return f[x] == x ? x : f[x] = find(f[x]);
59 }
60 void merge(int x, int y)
61 {
62     int fx = find(x);
63     int fy = find(y);
64     if (fx != fy)
65     {
66         f[fx] = fy;
67     }
68 }
69 bool check(node a, node b)
70 {
71     double dis = sqrt( (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
72     return dis <= d;
73 }
74 void init()
75 {
76     for (int i=1;i<=n;i++)
77         f[i] = i;
78 }
View Code

 

2、

The Suspects

 POJ - 1611

 

题意:学校里有n个学生,m个小组。小组中有一个人疑似得了冠状,那么全小组的人都可能得了。给出每个小组的成员,学生0疑似得了冠状,那么求有多少个人被怀疑。

思路:比第一题更普通的并查集。

代码:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 const int N = 3e4+5;
 6 int f[N], n, m;
 7 
 8 int find(int x);
 9 void merge(int x, int y);
10 void init();
11 
12 int main()
13 {
14     while (scanf("%d %d", &n, &m) && (n || m))
15     {
16         init();
17         for (int i=1;i<=m;i++)
18         {
19             int k, y;
20             scanf("%d", &k);
21             scanf("%d", &y);
22             for (int j=2;j<=k;j++)
23             {
24                 int x;
25                 scanf("%d", &x);
26                 merge(y, x);
27             }
28         }
29         int cnt = 0;
30         for (int i=0;i<n;i++)
31         {
32             if (find(i) == 0)
33                 cnt++;
34         }
35         if (m == 0) cnt = 1;
36         printf("%d\n", cnt);
37     }
38     return 0;
39 }
40 
41 int find(int x)
42 {
43     return f[x] == x ? x : f[x] = find(f[x]);
44 }
45 void merge(int x, int y)
46 {
47     int fx = find(x);
48     int fy = find(y);
49     if (fx != fy)
50         f[max(fx, fy)] = min(fx, fy);
51 
52 }
53 void init()
54 {
55     for (int i=0;i<n;i++)
56         f[i] = i;
57 }
View Code

 

3、

How Many Tables

 HDU - 1213 
 
题意:太直白了我都不想翻译了。
思路:比第二题输入更普通的并查集。
代码:
 1 #include <cstdio>
 2 #include <cstring>
 3 using namespace std;
 4 const int N = 1010;
 5 int T, n, m, f[N];
 6 bool vis[N];
 7 
 8 int find(int x);
 9 void merge(int x, int y);
10 void init();
11 
12 int main()
13 {
14     scanf("%d", &T);
15     while (T--)
16     {
17         memset(vis, 0, sizeof(vis));
18         scanf("%d %d", &n, &m);
19         init();
20         for (int i=1;i<=m;i++)
21         {
22             int x, y;
23             scanf("%d %d", &x, &y);
24             merge(x, y);
25         }
26         int cnt = 0;
27         for (int i=1;i<=n;i++)
28         {
29             int k = find(i);
30             if (vis[k] == false)
31             {
32                 vis[k] = true;
33                 cnt++;
34             }
35         }
36         printf("%d\n", cnt);
37     }
38     return 0;
39 }
40 
41 int find(int x)
42 {
43     return f[x] == x ? x : f[x] = find(f[x]);
44 }
45 void merge(int x, int y)
46 {
47     int fx = find(x);
48     int fy = find(y);
49     if (fx != fy)
50         f[fx] = fy;
51 }
52 void init()
53 {
54     for (int i=1;i<=n;i++)
55         f[i] = i;
56 }
View Code

哟,这不并查集吗?几天不见,这么拉了?

 

4、

How Many Answers Are Wrong

 HDU - 3038 

 我错了,并查集哥

题意:给出n和m,再给出m次l, r, num, 表示在一个数组中a[l]+...+a[m]的值为num。要求判断有几句语句是错误的。
思路:实在没有思路,于是翻了kuangbin的博客。看到了之间的量化关系就懂了。其实这题是很常见的并查集题型,只是题目有点抽象。只要把给出的语句理解为一个前缀和数组,sum[r] - sum[l - 1] = num。于是就可以用带权并查集做了。用rela数组记录当前节点与父结点之间的差值。
代码:
 1 #include <cstdio>
 2 #include <cstring>
 3 using namespace std;
 4 const int N = 2e5+5;
 5 int f[N], rela[N], n, m;
 6 
 7 void init();
 8 void merge(int x, int y, int num);
 9 int find(int x);
10 bool check(int x, int y, int num);
11 
12 int main()
13 {
14     while (~scanf("%d %d", &n, &m))
15     {
16         init();
17         int l, r, num, cnt = 0;
18         for (int i=1;i<=m;i++)
19         {
20             scanf("%d %d %d", &l, &r, &num);
21             if (check(l-1, r, num))
22             {
23                 merge(l-1, r, num);
24             }
25             else
26                 cnt++;
27 
28         }
29         printf("%d\n", cnt);
30     }
31     return 0;
32 }
33 
34 void init()
35 {
36     for (int i=1;i<=n;i++)
37         f[i] = i, rela[i] = 0;
38 }
39 void merge(int x, int y, int num)
40 {
41     int fx = find(x);
42     int fy = find(y);
43     if (fx != fy)
44     {
45         f[fy] = fx;
46         rela[fy] = rela[x] + num - rela[y];
47     }
48 }
49 int find(int x)
50 {
51     if (f[x] == x) return x;
52     int temp = f[x];
53     f[x] = find(f[x]);
54     rela[x] = rela[x] + rela[temp];
55     return f[x];
56 }
57 bool check(int x, int y, int num)
58 {
59     int fx = find(x);
60     int fy = find(y);
61     if (fx != fy)
62         return true;
63     return num == (rela[y] - rela[x]);
64 }
View Code

总结:似乎只要这种题型,能量化给出语句之间的关系就可以用带权并查集做了。带权并查集在维护关系数组rela时

  在find()函数中,要维护x和x最终的父结点中间每一个节点的权值,这里用的是每个节点本身的权值来更新。

  在merge()函数中,要维护fx 和 fy其中一个变成子节点的节点的权值。需要rela[l], rela[r], num。其中num就是量化的关系。

 

5、

 

食物链

 POJ - 1182 

题意:和上题一样是找错误语句的个数。关系只有吃和不吃。

思路:也是先量化关系,再利用带权并查集。

代码:

 

 1 #include <cstdio>
 2 #include <cstring>
 3 using namespace std;
 4 const int N = 5e4+5;
 5 int n, k, d, x, y, f[N], rela[N];
 6 
 7 inline int read();
 8 int find(int x);
 9 void merge(int x, int y, int d);
10 bool check(int c, int x, int y);
11 void init();
12 
13 int main()
14 {
15     n = read(); k = read();
16     init();
17     int cnt = 0;
18     for (int i=1;i<=k;i++)
19     {
20         d = read(); x = read(); y = read();
21         d--;
22         if (check(d, x, y))
23             merge(x, y, d);
24         else
25             cnt++;
26     }
27     printf("%d\n", cnt);
28     return 0;
29 }
30 
31 void init()
32 {
33     for (int i=1;i<=n;i++)
34         f[i] = i, rela[i] = 0;
35 }
36 bool check(int d, int x, int y)
37 {
38     if (x > n || y > n || (d == 2 && x == y))
39         return false;
40     if (find(x) == find(y))
41         return ((rela[x] - rela[y] + 3) % 3 ) == d;
42     else
43         return true;
44 }
45 int find(int x)
46 {
47     if (x == f[x]) return x;
48     int t = f[x];
49     f[x] = find(f[x]);
50     rela[x] = (rela[x] + rela[t]) % 3;
51     return f[x];
52 }
53 void merge(int x, int y, int d)
54 {
55     int fx = find(x);
56     int fy = find(y);
57     if (fx != fy)
58     {
59         f[fx] = fy;
60         rela[fx] = (rela[y] - rela[x] + d + 3) % 3;
61     }
62 }
63 
64 inline int read()
65 {
66     int x = 0, w = 1;
67     char ch = 0;
68     while (ch < '-')
69     {
70         if (ch == '-')
71             w = -1;
72         ch = getchar();
73     }
74     while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
75     return x * w;
76 }
View Code

 

 

 

总结:我是先做的这一题再做的第4题,这一题时对带权并查集认识还不深。所以也是看了别人的博客,这里面不但讲了带权并查集,也列了表描述这一题的关系,看题解还是推荐看这个。

 

6、

True Liars

 POJ - 1417 

题意:一群天使和恶魔住在一个岛上,天使只说真话,恶魔只说假话。向xi号人问第yi号人是否是天使并得到回答。问能否得到哪一部分人是天使,只能有唯一解。

思路:思考一下可知,若回答为yes则两人为同类,回答为no则两人为异类。用带权并查集可以将他们分为一个个集合,每个集合中有两部分人。然后将这些集合组成起来看是否能得到解。用动态规划可以得出解的最大数量。如果不是1,则打印no。如果是1,那我也不知道怎么办了。这题太考验编码能力了,思路倒是比较简单。

半成品代码:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <vector>
 4 using namespace std;
 5 const int N = 1010, M = 305;
 6 int n, p1, p2, f[M], rela[M], dp[M][M], a[M], b[2][M];
 7 bool vis[M];
 8 int find(int x);
 9 void init();
10 
11 int main()
12 {
13     while (~scanf("%d %d %d", &n, &p1, &p2) && n && p1 && p2)
14     {
15         init();
16         while (n--)
17         {
18             int x, y; char s[10];
19             scanf("%d %d %s", &x, &y, s);
20             int flag = (s[0] == 'y');
21             int fx = find(x), fy = find(y);
22             if (fx != fy)
23             {
24                 f[fy] = fx;
25                 rela[fy] = (rela[x] + rela[y] + flag) % 2;
26             }
27         }
28         int cnt = 0;
29         memset(vis, 0, sizeof(vis));
30         memset(b, 0, sizeof(b));
31         for (int i=1;i<=p1+p2;i++)
32         {
33             if (vis[f[i]] == 0)
34             {
35                 vis[f[i]] = true;
36                 a[++cnt] = f[i];
37             }
38             b[rela[i]][f[i]]++;
39         }
40         for (int i=1;i<=cnt;i++)
41         {
42             for (int j=p1+p2;j>=1;j--)
43             {
44                 if (j >= b[0][a[i]] && dp[i-1][j-b[0][a[i]]])
45                     dp[i][j] += dp[i-1][j-b[0][a[i]]];
46                 if (j >= b[1][a[i]] && dp[i-1][j-b[1][a[i]]])
47                     dp[i][j] += dp[i-1][j-b[1][a[i]]];
48             }
49         }
50         if (dp[cnt][p1] != 1)
51         {
52             puts("no");
53             continue;
54         }
55         
56     }
57     return 0;
58 }
59 
60 int find(int x)
61 {
62     if (f[x] == x)
63         return x;
64     int temp = f[x];
65     f[x] = find(f[x]);
66     rela[x] = (rela[x] + rela[temp]) % 2;
67     return f[x];
68 }
69 void init()
70 {
71     for (int i=1;i<=p1+p2;i++)
72         f[i] = i, rela[i] = 0;
73 }
View Code

总结:我要好好去提升自己的编码能力了,等我足够强了会回来补这题的(大概)。

 

7、

Supermarket

 POJ - 1456

题意:超市有n样东西要卖,每个东西都有自己的价值和过期时间。一天只能卖一样东西,过期后无法再卖。问能卖到最多多少钱?

思路1:贪心:

  对物品以价值从大到小排序。按顺序从价值最大的物品到价值最小的物品,对每一个物品从过期时间开始向前寻找没有卖东西的日子,在可卖出的最晚时间卖出。虽然是O(n^2)也可以AC。

代码1:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 const int N = 1e4+5;
 6 struct product
 7 {
 8     int p, d;
 9 } a[N];
10 int n;
11 bool vis[N];
12 
13 bool cmp(product a, product b)
14 {
15     return a.p > b.p;
16 }
17 
18 int main()
19 {
20     while (~scanf("%d", &n))
21     {
22         memset(vis, 0, sizeof(vis));
23         for (int i=1; i<=n; i++)
24             scanf("%d %d", &a[i].p, &a[i].d);
25         sort(a+1, a+1+n,cmp);
26         int ans = 0;
27         for (int i=1; i<=n; i++)
28         {
29             if (vis[a[i].d])
30             {
31                 for (int j=a[i].d - 1; j >= 1; j--)
32                 {
33                     if (!vis[j])
34                     {
35                         vis[j] = true;
36                         ans += a[i].p;
37                         break;
38                     }
39                 }
40             }
41             else
42             {
43                 ans += a[i].p;
44                 vis[a[i].d] = true;
45             }
46         }
47         printf("%d\n", ans);
48     }
49     return 0;
50 }
View Code 

思路2:用并查集优化思路1的代码。

  用f数组记录最近的空闲日期。当前日期被使用后,将其向前移,寻找最近的空闲日期,并记录。说是并查集优化,其实是优化了路径。

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 const int N = 1e4+5;
 6 struct product
 7 {
 8     int p, d;
 9 } a[N];
10 int n;
11 int f[N];
12 
13 bool cmp(product a, product b)
14 {
15     return a.p > b.p;
16 }
17 int find(int x)
18 {
19     return f[x] == -1 ? x : f[x] = find(f[x]);
20 }
21 
22 int main()
23 {
24     while (~scanf("%d", &n))
25     {
26         memset(f, -1, sizeof(f));
27         for (int i=1;i<=n;i++)
28             scanf("%d %d", &a[i].p, &a[i].d);
29         sort(a+1, a+n+1, cmp);
30         int ans = 0;
31         for (int i=1;i<=n;i++)
32         {
33             int d = find(a[i].d);
34             if (d > 0)
35             {
36                 f[d] = d - 1;
37                 ans += a[i].p;
38             }
39         }
40         printf("%d\n", ans);
41     }
42     return 0;
43 }
View Code

 

思路3:动态规划,人称小贪心。耗时大大增加

  状态:dp[j]表示卖出j件物品得到的价值最大值。

  状态转移方程:dp[j] = max(dp[j], dp[j-1]+v[i])。

  细节:每样东西可卖可不卖,就是01背包。加上时间限制的话,第二层循环就在d[i]...1即可。

代码:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 const int N = 1e4+5;
 6 struct product
 7 {
 8     int p, d;
 9 } a[N];
10 int n, dp[N];
11 
12 bool cmp(product a, product b)
13 {
14     return a.d < b.d;
15 }
16 
17 int main()
18 {
19     while (~scanf("%d", &n))
20     {
21         memset(dp, 0, sizeof(dp));
22         for (int i=1;i<=n;i++)
23             scanf("%d %d", &a[i].p, &a[i].d);
24         sort(a+1, a+1+n, cmp);
25         int ans = 0;
26         for (int i=1;i<=n;i++)
27             for (int j=a[i].d;j > 0;j--)
28             {
29                 dp[j] = max(dp[j], dp[j-1]+a[i].p), ans = max(ans, dp[j]);
30             }
31         printf("%d\n", ans);
32     }
33     return 0;
34 }
View Code

 

8、

Parity game POJ - 1733 (离散化+带权并查集)

9、

Navigation Nightmare POJ - 1984(带权并查集)

10、

A Bug's Life

 POJ - 2492

代码:

 1 #include <cstdio>
 2 #include <cstring>
 3 using namespace std;
 4 const int N = 2020;
 5 int n, m, f[N], rela[N];
 6 bool flag;
 7 
 8 void init();
 9 int find(int x);
10 void solve(int x, int y);
11 
12 int main()
13 {
14     int T;
15     scanf("%d", &T);
16     for (int I=1;I<=T;I++)
17     {
18         flag = false;
19         scanf("%d%d", &n, &m);
20         init();
21         for (int i=1;i<=m;i++)
22         {
23             int x, y;
24             scanf("%d %d", &x, &y);
25             if (flag) continue;
26             solve(x, y);
27         }
28         printf("Scenario #%d:\n", I);
29         if (flag)
30             printf("Suspicious bugs found!\n");
31         else
32             printf("No suspicious bugs found!\n");
33         puts("");
34     }
35     return 0;
36 }
37 
38 void init()
39 {
40     for (int i=1;i<=n;i++)
41         f[i] = i, rela[i] = 0;
42 }
43 int find(int x)
44 {
45     if (f[x] == x) return x;
46     int temp = f[x];
47     f[x] = find(f[x]);
48     rela[x] = (rela[x] + rela[temp]) % 2;
49     return f[x];
50 }
51 void solve(int x, int y)
52 {
53     int fx = find(x), fy = find(y);
54     if (fx != fy)
55     {
56         f[fy] = fx;
57         rela[fy] = (rela[x] + rela[y] + 1) % 2;
58     }else
59     {
60         if (rela[x] == rela[y])
61             flag = true;
62     }
63 }
View Code

这题离谱,这个代码700+ms,用了快读1500+ms,poj上却有0ms的。不懂=。=

11、

 

posted @ 2020-04-20 21:40  FantaDevourer  阅读(133)  评论(0编辑  收藏  举报