Codeforces Round #505 (Div 1 + Div 2 Combined) Solution
从这里开始
Problem A Doggo Recoloring
题目大意
给定一个只包含小写字母的字符串,每次可以将一种至少出现了2次的字符都变成另一个字符。问是否可能使所有字符一样。
特判$n = 1$的时候。其他时候看有没有一个字符出现了两次。
Code
1 /** 2 * Codeforces 3 * Problem#1025D 4 * Accepted 5 * Time: 31ms 6 * Memory: 100k 7 */ 8 #include <bits/stdc++.h> 9 using namespace std; 10 typedef bool boolean; 11 12 const int N = 1e5 + 5; 13 14 int n; 15 char str[N]; 16 boolean aflag = false; 17 boolean vis[30]; 18 19 int main() { 20 scanf("%d", &n); 21 scanf("%s", str); 22 if (n == 1) { 23 puts("YES"); 24 return 0; 25 } 26 for (int i = 0; i < n; i++) 27 if (vis[str[i] - 'a']) { 28 puts("YES"); 29 return 0; 30 } else 31 vis[str[i] - 'a'] = true; 32 puts("NO"); 33 return 0; 34 }
Problem B Weakened Common Divisor
题目大意
给定$n$个二元组$(a_{i}, b_{i})$,问是否存在一个$d > 1$,使得对于每个$1\leqslant i \leqslant n$满足$d \mid a_{i}$或者$d \mid b_{i}$。
首先讲一个沙雕做法。
暴力枚举$a_{1} \times b_{1}$的质因子。然后$O(n)$检查。时间复杂度$O(\sqrt{V} + n\log{V})$。
然后讲考场上我的zz做法。
如果$d$满足条件,那么$d$能够整除$lcm(a_{i}, b_{i})$。就是说$d$能整除每一对的最小公倍数的最大公约数$g$。
然后找它较小的约数就是答案(因为上面那句话只是必要条件,但不是充分条件,比如可能$d > a_{i}$且$d > b_{i}$,但$d\mid a_{i}b_{i}$)。(少了这一步 WA * 1)
找这个合法的约数不能暴力找(然后暴力找能过pretest,后来发现resubmission * 1)
然后发现每个数有用的部分其实是它和这个$g$的最大公约数,取这中间非1的最小值就行了。
Code
1 /** 2 * Codeforces 3 * Problem#1025B 4 * Accepted 5 * Time: 139ms 6 * Memory: 2400k 7 */ 8 #include<bits/stdc++.h> 9 #define ll long long 10 using namespace std; 11 12 const int N = 150005; 13 14 int n; 15 int a[N], b[N]; 16 ll ls[N]; 17 18 ll gcd(ll a, ll b) { 19 return (!b) ? (a) : (gcd(b, a % b)); 20 } 21 22 int main(){ 23 scanf("%d", &n); 24 25 for (int i = 1; i <= n; i++) 26 scanf("%d%d", a + i, b + i); 27 28 for (int i = 1; i <= n; i++) 29 ls[i] = a[i] / gcd(a[i], b[i]) * b[i]; 30 ll res = ls[1]; 31 32 for (int i = 2; i <= n; i++) 33 res = gcd(res, ls[i]); 34 for (int i = 1; i <= n; i++) { 35 ll x = gcd(res, a[i]); 36 if (x > 1) 37 res = min(res, x); 38 ll y = gcd(res, b[i]); 39 if (y > 1) 40 res = min(res, y); 41 } 42 43 if (res == 1) 44 cout << -1; 45 else 46 cout << res; 47 return 0; 48 }
Problem C Plasticine zebra
题目大意
给定一个只含 'b','w' 的串,每操作可以选择两个字符间的一个位置,然后将两边翻转。问可以得到的最长的连续的交错的串的长度。
来手玩一下这个操作。
$ab|c \rightarrow b^{T}|a^{T}c^{T}\rightarrow bca$。
然后发现任意两次连续的操作会使得某一段被移动到字符串的最右端。
如果操作只进行奇数次?比如:$ab|cd \rightarrow b^{T}a^{T}d^{T}c^{T}$,其中$a^{T}d^{T}$是最优的答案。
所以$a$的第一个字符和$d$的最后一个字符不同,我们可以将$abcd \rightarrow bcda$,这样可以得到同样答案(至是串的顺序反了一下)。
因此可以将原串复制一份粘在后面,然后暴力跑答案。答案记得和$n$取最小值。
Code
1 /** 2 * Codeforces 3 * Problem#1025 4 * Accepted 5 * Time: 31ms 6 * Memory: 300k 7 */ 8 #include<bits/stdc++.h> 9 using namespace std; 10 11 const int N = 1e5 + 9; 12 13 char str[N << 1]; 14 int main(){ 15 scanf("%s", str + 1); 16 int n = strlen(str + 1), res = 1, cnt = 1; 17 for (int i = 1; i <= n; i++) 18 str[i + n] = str[i]; 19 str[2 * n] = str[2 * n - 1]; 20 for (int i = 1; i < 2 * n; i++) 21 if (str[i] != str[i + 1]) 22 cnt++; 23 else 24 res = max(res, cnt), cnt = 1; 25 printf("%d\n", min(res, n)); 26 return 0; 27 }
Problem D Recovering BST
题目大意
给定一个严格单调递增的数组,问能否构成一个二叉搜索树使得一条边的两端点的键值的最大公约数大于1。
noip普及组难度?
考虑区间动态规划。用$f[i][j]$表示区间$[l, r]$内的数能否构成一棵二叉树。
不。这是有问题的。我们还需要涉及它的父节点。但是它要么是$l - 1$,要么是$r + 1$,因此只需再增加一个$[0 / 1]$就行了。
每次转移枚举当前区间内的根。
由于残暴的$O(n^3)$数据范围有点可怕。不敢多带个$log$。预处理任意两个数的$gcd$。
Code
1 /** 2 * Codeforces 3 * Problem#1025D 4 * Accepted 5 * Time: 93ms 6 * Memory: 3900k 7 */ 8 #include <bits/stdc++.h> 9 using namespace std; 10 typedef bool boolean; 11 12 const int N = 705; 13 14 int gcd(int a, int b) { 15 return (!b) ? (a) : (gcd(b, a % b)); 16 } 17 18 int n; 19 int ar[N]; 20 int gs[N][N]; 21 boolean vis[N][N][2]; 22 boolean f[N][N][2]; 23 24 inline void init() { 25 scanf("%d", &n); 26 for (int i = 1; i <= n; i++) 27 scanf("%d", ar + i); 28 for (int i = 1; i <= n; i++) 29 for (int j = 1; j <= n; j++) 30 gs[i][j] = gcd(ar[i], ar[j]); 31 } 32 33 boolean dp(int l, int r, int dir, int rt) { 34 if (l > r) 35 return true; 36 if (vis[l][r][dir]) 37 return f[l][r][dir]; 38 vis[l][r][dir] = true; 39 for (int i = l; i <= r && !f[l][r][dir]; i++) 40 if (!rt || gs[rt][i] > 1) 41 f[l][r][dir] |= dp(l, i - 1, 1, i) && dp(i + 1, r, 0, i); 42 return f[l][r][dir]; 43 } 44 45 inline void solve() { 46 if (dp(1, n, 0, 0)) 47 puts("Yes"); 48 else 49 puts("No"); 50 } 51 52 int main() { 53 init(); 54 solve(); 55 return 0; 56 }
Problem E Colored Cubes
题目大意
给定一个$n\times n$的网格图,图上有$m$个两两位置不同的箱子,它们各有一个目标位置,每次可以将一个箱子移动它相邻的一个空格子(不能移出边界)。要求在少于10800步内将所有箱子移动到它对应的位置。
开始一直以为是一道图论题。原来是构造题。下来想想如果要水过去,感觉不是很难。
$m\leqslant n$是个优秀的性质。主对角线有着优美的性质(比如每个箱子可以向四个方向移动)。
考虑把所有箱子先移动到主对角线上。显然这样是可行的。对于主对角线上的每个空位置你一定可以找到一个不在主对角线上的箱子能够移动到它(如果还有箱子不在主对角线上,从它开始bfs就能找到)。
然后对主对角线上的箱子按它们对应的目标位置进行排序,第一关键字是横坐标,第二关键字是纵坐标。交换两个主对角线上的箱子可以通过将一个先移开,另一个移到它原来的位置上,再把它移动过去完成交换。用选择排序 / 置换 / 冒泡排序可以做到移动总次数$O(n^{2})$。
考虑把每个箱子移到它目标的那一行上,按照横坐标的顺序从左边开始排列。简单说明一下一定可行。因为每一个箱子所在的那一列是一定通畅的。
最后一步就特别简单了。每一行从后向前把个箱字移动到它所在的位置上。
因为我比较菜,移动总次数$O(n^{2})$但常数有点大,第一步移动需要贪一下心才能满足10800步的限制。
标算大概是先移动箱子使得$x$坐标互不相同,然后将所有箱子移到对角线上,然后用同样的方法将目标位置的箱子移动到对角线上,再将这些操作反转。它能保证移动总次数不超过$4\times n^{2}$。
Code
1 /** 2 * Codeforces 3 * Problem#1025E 4 * Accepted 5 * Time: 78ms 6 * Memory: 600k 7 */ 8 #include <bits/stdc++.h> 9 using namespace std; 10 typedef bool boolean; 11 12 #define ppp pair<Point, Point> 13 #define fi first 14 #define sc second 15 16 const int N = 55; 17 18 typedef class Point { 19 public: 20 int x, y; 21 22 Point (int x = 0, int y = 0):x(x), y(y) {} 23 24 boolean operator < (Point b) const { 25 if (x ^ b.x) return x < b.x; 26 return y < b.y; 27 } 28 }Point; 29 30 void readPoint(Point& p) { 31 scanf("%d%d", &p.x, &p.y); 32 } 33 34 #define pv(_array, _pos) ((_array)[_pos.x][_pos.y]) 35 36 int n, m; 37 boolean used[N][N], sec[N]; 38 Point ps[N], pt[N]; 39 pair<Point, int> buf[N], bs[N]; 40 int place[N], rk[N], put[N]; 41 42 inline void init() { 43 scanf("%d%d", &n, &m); 44 for (int i = 1; i <= m; i++) { 45 readPoint(ps[i]); 46 bs[i] = make_pair(ps[i], i); 47 if (ps[i].x == ps[i].y && ps[i].x <= m) 48 place[ps[i].x] = i, sec[i] = true; 49 pv(used, ps[i]) = true; 50 } 51 for (int i = 1; i <= m; i++) { 52 readPoint(pt[i]); 53 buf[i] = make_pair(pt[i], i); 54 } 55 } 56 57 const int mov[4][2] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}}; 58 59 boolean inq[N][N]; 60 Point ls[N][N]; 61 queue<Point> que; 62 boolean arrivable(Point s, Point t) { 63 memset(inq, false, sizeof(inq)); 64 inq[s.x][s.y] = true; 65 que.push(s); 66 while (!que.empty()) { 67 Point e = que.front(); 68 que.pop(); 69 for (int k = 0; k < 4; k++) { 70 Point eu (e.x + mov[k][0], e.y + mov[k][1]); 71 if (eu.x < 1 || eu.x > n || eu.y < 1 || eu.y > n) 72 continue; 73 if (used[eu.x][eu.y] || inq[eu.x][eu.y]) 74 continue; 75 ls[eu.x][eu.y] = e; 76 inq[eu.x][eu.y] = true; 77 que.push(eu); 78 } 79 } 80 return inq[t.x][t.y]; 81 } 82 83 vector< pair<Point, Point> > path; 84 void printPath(Point cur, Point s) { 85 if (cur.x == s.x && cur.y == s.y) 86 return; 87 Point nx = ls[cur.x][cur.y]; 88 printPath(nx, s); 89 path.push_back(ppp(nx, cur)); 90 } 91 92 boolean move(Point start, Point end) { 93 if (!arrivable(start, end)) 94 return false; 95 printPath(end, start); 96 pv(used, start) = false; 97 pv(used, end) = true; 98 return true; 99 } 100 101 void swapPoint(int a, int b) { 102 move(Point(a, a), Point(a, b)); 103 move(Point(b, b), Point(a, a)); 104 move(Point(a, b), Point(b, b)); 105 swap(place[a], place[b]); 106 } 107 108 int cnt[N]; 109 boolean finished[N]; 110 111 inline void solve() { 112 sort(bs + 1, bs + m + 1); 113 sort(buf + 1, buf + m + 1); 114 for (int i = 1; i <= m; i++) 115 rk[buf[i].second] = i; 116 117 for (int i = 1; i <= m; i++) { 118 if (place[i]) 119 continue; 120 boolean aflag = false; 121 for (int k = 1; k <= m && !aflag; k++) { 122 int j = bs[k].second; 123 if (!sec[j] && move(ps[j], Point(i, i))) { 124 ps[j] = Point(i, i); 125 place[i] = j, sec[j] = true; 126 } 127 } 128 } 129 130 // for (int i = 1; i <= m; i++) 131 // cerr << place[i] << " "; 132 // cerr << "==============" << endl; 133 134 for (int i = 1; i <= m; i++) { 135 int pos = 0; 136 for (int j = i; j <= m; j++) 137 if (rk[place[j]] == i) { 138 pos = j; 139 break; 140 } 141 if (pos ^ i) 142 swapPoint(i, pos); 143 // cerr << i << " " << place[i] << endl; 144 } 145 146 for (int i = 1; i <= m; i++) { 147 int x = buf[i].first.x; 148 move(Point(i, i), Point(x, ++cnt[x])); 149 ps[buf[i].second] = Point(x, cnt[x]); 150 } 151 152 for (int i = m; i; i--) 153 move(ps[buf[i].second], buf[i].first); 154 155 printf("%d\n", (signed) path.size()); 156 for (int i = 0; i < (signed) path.size(); i++) 157 printf("%d %d %d %d\n", path[i].first.x, path[i].first.y, path[i].second.x, path[i].second.y); 158 } 159 160 int main() { 161 srand(233u); 162 init(); 163 solve(); 164 return 0; 165 }
Problem F Disjoint Triangles
题目大意
给定平面上$n$个不同点,问存在多少对以给定点为顶点的两个三角形使得它们没有公共部分。
感觉没法补集转化之类的。考虑选取恰当的计数点。
考虑经过两个三角形的两个端点的直线,且能将两个三角形划分到两个平面内。
对于任意一对合法的三角形恰好存在2条这样的直线(画画图可以发现,我也不会证明qwq)。
每条这样的切线有2种选法(每个点都可以和左侧的点构成三角形也可以和右侧)。可以规定它只和左侧的点构成三角形(枚举直线的时候要定向)。
这样可以$O(n^{3})$解决掉这个问题。
考虑枚举一个点,每次乱枚举另一个点的时候很浪费。直接排极角序,然后就可以线性扫一圈了。
时间复杂度$O(n^{2}\log n)$
Code
1 /** 2 * Codeforces 3 * Problem#1025F 4 * Accepted 5 * Time: 607ms 6 * Memory: 100k 7 */ 8 #include <bits/stdc++.h> 9 using namespace std; 10 typedef bool boolean; 11 #define ll long long 12 13 const double eps = 1e-8; 14 15 typedef class Point { 16 public: 17 double x, y; 18 19 Point(double x = 0.0, double y = 0.0):x(x), y(y) { } 20 }Point, Vector; 21 22 Point operator - (Point a, Point b) { 23 return Point(a.x - b.x, a.y - b.y); 24 } 25 26 double cross(Vector u, Vector v) { 27 return u.x * v.y - u.y * v.x; 28 } 29 30 int n; 31 Point* ps; 32 33 inline void init() { 34 scanf("%d", &n); 35 ps = new Point[(n + 1)]; 36 for (int i = 1; i <= n; i++) 37 scanf("%lf%lf", &ps[i].x, &ps[i].y); 38 } 39 40 int cnt = 0; 41 ll res = 0; 42 pair<double, int> *ar; 43 boolean *vis; 44 45 int getNext(int p) { 46 if (p == cnt) 47 return 1; 48 return p + 1; 49 } 50 51 inline void solve() { 52 vis = new boolean[(n + 1)]; 53 ar = new pair<double, int> [(n + 1)]; 54 for (int i = 1; i <= n; i++) { 55 cnt = 0; 56 for (int j = 1; j <= n; j++) 57 if (j ^ i) { 58 Vector dif = ps[j] - ps[i]; 59 ar[++cnt] = make_pair(atan2(dif.y, dif.x), j); 60 } 61 sort(ar + 1, ar + cnt + 1); 62 int p = 1, cntl = 0; 63 memset(vis, false, sizeof(boolean) * (n + 1)); 64 for (int j = 1; j < n; j++) { 65 Vector dif = ps[ar[j].second] - ps[i]; 66 for (int np = getNext(p); np != j && cross(dif, ps[ar[np].second] - ps[i]) > 0; p = np, np = getNext(np)) 67 cntl += !vis[np], vis[np] = true; 68 res = res + cntl * 1ll * (cntl - 1) / 2 * (n - cntl - 2) * 1ll * (n - cntl - 3) / 2; 69 // cntl--; 70 int np = ar[getNext(j)].second, nj = getNext(j); 71 if (cross(dif, ps[np] - ps[i]) > 0) 72 cntl--, vis[nj] = false; 73 else { 74 if (p == j) 75 p = getNext(j); 76 } 77 } 78 // cerr << i << " " << res << endl; 79 } 80 81 cout << (res >> 1) << endl; 82 } 83 84 int main() { 85 init(); 86 solve(); 87 return 0; 88 }
Problem G Company Acquisitions
题目大意
有$n$个公司,每个公司要么是独立的,要么被一个独立的公司收购。
如果存在不止1个公司是独立的,每天那么会随机选择两个独立的公司$A,B$,然后有二分之一的概率$A$被$B$收购,二分之一的概率$B$被$A$收购。如果$A$被$B$收购,那么$A$所有收购的公司会变成独立的。
给定初始局面,问只存在1个独立的公司期望所需要的天数。
定义一个收购了$k$个公司$A$的势能为$2^{k} - 1$。定义一个局面的势能为所有公司的势能的和。
考虑选择了两个分别收购了$p,q$个公司的公司后势能变化量的期望:$\frac{1}{2}\left[2^{p + 1}- 1 - (2^{p} - 1 + 2^{q} - 1)\right] + \frac{1}{2}\left[2^{q + 1}-1-(2^{p} - 1 + 2^{q} - 1) \right ] = 1$。
所以$E\left ( \Delta \Phi \right )=1$。
我们知道目标局面的势能是$2^{n - 1} - 1$。然后可以计算期望的步数了。
(其实是考虑硬点每个状态的期望要的步数,然后可以发现可以满足一堆方程)
Code
1 /** 2 * Codeforces 3 * Problem#1025G 4 * Accepted 5 * Time: 31ms 6 * Memory: 300k 7 */ 8 #include <iostream> 9 #include <cstdio> 10 using namespace std; 11 typedef bool boolean; 12 13 const int N = 505, M = 1e9 + 7; 14 15 int sub(int a, int b) { 16 a -= b; 17 if (a < 0) 18 return a + M; 19 return a; 20 } 21 22 int qpow(int a, int p) { 23 int rt = 1, pa = a; 24 for ( ; p; p >>= 1, pa = pa * 1ll * pa % M) 25 if (p & 1) 26 rt = rt * 1ll * pa % M; 27 return rt; 28 } 29 30 int n; 31 inline void init() { 32 scanf("%d", &n); 33 } 34 35 int res; 36 int follower[N]; 37 inline void solve() { 38 res = sub(qpow(2, n - 1), 1); 39 for (int i = 1, x; i <= n; i++) { 40 scanf("%d", &x); 41 if (x != -1) 42 follower[x]++; 43 } 44 for (int i = 1; i <= n; i++) 45 res = sub(res, sub(qpow(2, follower[i]), 1)); 46 printf("%d\n", res); 47 } 48 49 int main() { 50 init(); 51 solve(); 52 return 0; 53 }