AtCoder Beginner Contest 282
《Make Bipartite 2》
思维,二分图

这个简单图可以有两种情况:
1.全部点都通过边连起来,即连通分量只有一个,其自己
2.还有有些点没有全部连起来,即有多个连通分量
1.不管上面哪一种情况,如果对图跑一个二分图染色O(n+m),如果染色失败,则都是返回0,因为这时,不管再连上那一边都不是二分图了
下面我们保证跑一个二分图染色O(n+m),如果染色成功:
2.现在我们假设各个点之间都有边,即一共有n*(n-1)/2条边
(1)对于只有一个连通分量来说: 连上相同颜色的点的边和已经连好的边(m),都是使二分图不合法的,除此之外都是合法的
则假设这个连通分量上有w个白点,b个黑点,则不合法的边为 w*(w-1)/2+b*(b-1)/2+m条
则正确答案为ans=n*(n-1)/2-(w*(w-1)/2+b*(b-1)/2+m);
(2)对于有多个连通分量来说:两个不同的连通分量上的任意点相连,都还是二分图
对于相同连通分量上的相同颜色的点相连是不合法的,同时已经连好的边(m)也是不合法的
于是:
这里循环相加的是各个连通分量上黑点相连的边数与白点相连的边数
即我们在写dfs跑二分图染色时,可以返回这个连通分量上黑点与白点的个数
1 #include <iostream>
2 #include <algorithm>
3 #include <cstring>
4 #include <vector>
5 using namespace std;
6 const int N = 2 * 1e5 + 2;
7 typedef long long ll;
8 typedef pair<int, int> PII;
9 int color[N], n, m;
10 vector<int> sides[N];
11 PII dfs(int x, int c)
12 {
13 PII p = {0, 0};
14 color[x] = c;
15 if (c == 1)
16 p.first++;
17 else
18 p.second++;
19 for (int i = 0; i < sides[x].size(); i++)
20 {
21 int j = sides[x][i];
22 PII res;
23 if (!color[j])
24 res = dfs(j, -c);
25 else if (color[j] == c)
26 // 表示不构成二分图
27 return {-1, -1};
28 if (res.first == -1 && res.second == -1)
29 return {-1, -1};
30 else
31 {
32 p.first += res.first;
33 p.second += res.second;
34 }
35 }
36 return p;
37 }
38 int main()
39 {
40 cin >> n >> m;
41 for (int i = 1; i <= m; i++)
42 {
43 int a, b;
44 scanf("%d%d", &a, &b);
45 sides[a].push_back(b), sides[b].push_back(a);
46 }
47 ll ans = (ll)n * (n - 1) / 2 - m;
48 for (int i = 1; i <= n; i++)
49 {
50 if (!color[i])
51 {
52 PII p = dfs(i, 1);
53 // p.first表示这个连通分量中染色白色的个数(color==1),p.second 表示这个连通分量中染色黑色的个数;
54 if (p.first == -1 && p.second == -1)
55 {
56 cout << 0;
57 return 0;
58 }
59 ans -= (ll)p.first * (p.first - 1) / 2;
60 ans -= (ll)p.second * (p.second - 1) / 2;
61 }
62 }
63 cout << ans;
64 return 0;
65 }
《E - Choose Two and Eat One 》
最大生成树,思维

首先这道题N很小,即使将全部气球之间的得分搞出来也就O(N*N)
然后题目描述当我们选择了一个气球时就要抛弃一个气球,另一个气球还可以用。
我们将气球看成点
将气球之间的得分看成边的权重
也就是所我们要从n*n的边中(每个点到其他各个点之间的边总共有n*n条),选出n-1条边(因为一旦选择了任意两个点之后,一定会有其中一个点不能再用了,最后真正选出的边为n-1条),这n-1条边可以连接上全部的点,同时权重之和是最大的
这不就是最大生成树吗?
1 #include <iostream>
2 #include <algorithm>
3 #include <cstring>
4 using namespace std;
5 typedef long long ll;
6 const int N = 502;
7 struct node
8 {
9 int len;
10 int from, to;
11 bool operator<(const node &t) const
12 {
13 return len > t.len;
14 }
15 } sides[N * N];
16 int arr[N], n, m, pos = 0;
17 int quick(int a, int b, int m)
18 {
19 int ans = 1;
20 while (b)
21 {
22 if (b & 1)
23 ans = ((ll)ans * a) % m;
24 b >>= 1;
25 a = ((ll)a * a) % m;
26 }
27 return ans;
28 }
29 int h[N];
30 int find(int x)
31 {
32 if (h[x] != x)
33 h[x] = find(h[x]);
34 return h[x];
35 }
36 ll Kruskal()
37 {
38 int count = 1;
39 ll ans = 0;
40 for (int i = 1; i <= n; i++)
41 h[i] = i;
42 for (int i = 1; i <= pos; i++)
43 {
44 int len = sides[i].len;
45 int from = sides[i].from, to = sides[i].to;
46 int ff = find(from), ft = find(to);
47 if (ff == ft)
48 continue;
49 else
50 {
51 count++;
52 h[ft] = ff;
53 ans += len;
54 if (count == n)
55 break;
56 }
57 }
58 return ans;
59 }
60 int main()
61 {
62 cin >> n >> m;
63 for (int i = 1; i <= n; i++)
64 scanf("%d", &arr[i]);
65 // 无向图的最大生成树
66 for (int i = 1; i <= n; i++)
67 for (int j = 1; j <= n; j++)
68 {
69 if (i != j)
70 {
71 int x = arr[i], y = arr[j];
72 int len = ((ll)quick(x, y, m) + quick(y, x, m)) % m;
73 sides[++pos] = {len, i, j};
74 }
75 }
76 sort(sides + 1, sides + pos + 1);
77 /* for (int i = 1; i <= pos; i++)
78 {
79 cout << sides[i].len << " " << sides[i].from << " " << sides[i].to << endl;
80 } */
81 cout << Kruskal();
82 return 0;
83 }
《F - Union of Two Sets》
思维


一道构造题,最先我被启发的是用线段树的方式来写:
想着N只有4000,那么我可以像线段树一样将[1,4000]不断分下去知道分到[1,1].....[4000,4000]
然后在分得的区间中找到两个区间可以使得满足[l1,r1]U[l2,r2]=[L,R]
但是我发现我错了:我天真地认为线段树的方法分的区间,对于任意给定一个区间来说都可以用两个分得的区间表示
但是这是错误的,因为比如:
我想求[2,4000],这个就不可能用线段树分的两个区间组成
因为线段树的分法:每次对数值一半分,这导致比如1与2要在十分深的地方才被分开,根本就没有连好的一整块

而且比如像 [1,2] [3,4] [5,6]..............
没有[2,3],[ 4,5]............
对于此,解决的方法是:因为N只有4000,给出[L,R],我们要用自己写的全部区间中的两个区间的和来组成[L,R]

给定数len,其必定可以用两个属于1~loglen+1的 2的幂 组成
但是这道题是构造题那就将想法搞的更好写代码一点:


1 #include <iostream>
2 #include <algorithm>
3 #include <cstring>
4 #include <cmath>
5 using namespace std;
6 const int N = 4002;
7 struct node
8 {
9 int l, r;
10 int num;
11 } arr[N][N];
12 int pos[N];
13 int n, q, m = 0;
14 void find(int l, int r)
15 {
16 int len = r - l + 1;
17 int k = log2(len);
18 int qlen = pow(2, k);
19 int ansl, ansr;
20 for (int i = 1; i <= pos[k]; i++)
21 {
22 if (arr[k][i].l == l)
23 {
24 ansl = arr[k][i].num;
25 break;
26 }
27 }
28 for (int i = 1; i <= pos[k]; i++)
29 {
30 if (arr[k][i].r == r)
31 {
32 ansr = arr[k][i].num;
33 break;
34 }
35 }
36 cout << ansl << " " << ansr << endl;
37 }
38 int main()
39 {
40 memset(pos, 0, sizeof(pos));
41 cin >> n;
42 for (int i = 0; i <= log2(n) + 1; i++)
43 for (int j = 1; j <= n; j++)
44 {
45 int len = pow(2, i);
46 if (j + len - 1 <= n)
47 {
48 m++;
49 arr[i][++pos[i]] = {j, j + len - 1, m};
50 }
51 }
52 cout << m << endl;
53 for (int i = 0; i <= log2(n) + 1; i++)
54 for (int j = 1; j <= pos[i]; j++)
55 cout << arr[i][j].l << " " << arr[i][j].r << endl;
56 cin >> q;
57 while (q--)
58 {
59 int l, r;
60 scanf("%d%d", &l, &r);
61 find(l, r);
62 }
63 return 0;
64 }

浙公网安备 33010602011771号