【比赛链接】
【题解链接】
**【A】**Doggo Recoloring
【思路要点】
- 首先当 N = 1 N=1 N=1,答案为 Y e s Yes Yes。
- 否则,当且仅当存在一个字符出现了至少两次,答案为 Y e s Yes Yes。
- 时间复杂度 O ( N ) O(N) O(N)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 100005; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n, cnt[256]; char s[MAXN]; int main() { int n; read(n); bool ans = n == 1; scanf("\n%s", s + 1); for (int i = 1; i <= n; i++) { if (cnt[s[i]]) ans = true; cnt[s[i]]++; } if (ans) printf("Yes\n"); else printf("No\n"); return 0; }
**【B】**Weakened Common Divisor
【思路要点】
- 令 c i = a i ∗ b i c_i=a_i*b_i ci=ai∗bi,令所有 c i c_i ci的 g c d gcd gcd为 g g g,当且仅当 g = 1 g=1 g=1,答案为 − 1 -1 −1。
- 否则, g g g的每一个质因数一定都可以作为答案,我们只需要找到 g g g的任意一个可以作为答案的因数即可,注意这里 g g g可能是两个大质数的乘积,因此不能直接质因数分解。
- 实际上我们只需要通过判断 g c d ( g , a i ) gcd(g,a_i) gcd(g,ai)是否为 1 1 1来判断 a i a_i ai是不是可取的,若 g c d ( g , a i ) ≠ 1 gcd(g,a_i)\ne1 gcd(g,ai)̸=1,则令 g = g c d ( g , a i ) g=gcd(g,a_i) g=gcd(g,ai),否则令 g = g c d ( g , b i ) g=gcd(g,b_i) g=gcd(g,bi),最终得到的 g g g即为答案。
- 时间复杂度 O ( N L o g V ) O(NLogV) O(NLogV)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 150005; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } long long x[MAXN], y[MAXN]; long long gcd(long long x, long long y) { if (y == 0) return x; else return gcd(y, x % y); } int main() { long long ans = 0; int n; read(n); for (int i = 1; i <= n; i++) { read(x[i]), read(y[i]); ans = gcd(x[i] * y[i], ans); } if (ans == 1) { printf("-1\n"); return 0; } for (int i = 1; i <= n; i++) { if (gcd(ans, x[i]) != 1) ans = gcd(ans, x[i]); else ans = gcd(ans, y[i]); } writeln(ans); return 0; }
**【C】**Plasticine zebra
【思路要点】
- 考虑将序列首尾相接,形成一个环。
- 题目中给出的操作实际上就是将环在某处断开,重新形成一个序列。
- 因此用 T w o P o i n t e r s Two Pointers TwoPointers求出环上最长的合法序列即可。
- 时间复杂度 O ( N ) O(N) O(N)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 200005; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } char s[MAXN]; int main() { scanf("\n%s", s + 1); int n = strlen(s + 1); for (int i = 1; i <= n; i++) s[i + n] = s[i]; int ans = 0, nxt = 1; for (int i = 1; i <= n; i++) { chkmax(nxt, i); while (nxt - i + 1 < n && s[nxt + 1] != s[nxt]) nxt++; chkmax(ans, nxt - i + 1); } writeln(ans); return 0; }
**【D】**Recovering BST
【思路要点】
- 记 d p i , j , 0 / 1 dp_{i,j,0/1} dpi,j,0/1表示用 a i , a i + 1 , a i + 2 , . . . , a j a_i,a_{i+1},a_{i+2},...,a_j ai,ai+1,ai+2,...,aj是否能够构成一棵满足条件的二叉树,并且其根节点与 a i − 1 / a j + 1 a_{i-1}/a_{j+1} ai−1/aj+1互质。
- 预处理数字之间的互质关系,简单区间 D P DP DP即可。
- 时间复杂度 O ( N 3 ) O(N^3) O(N3)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 705; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n, a[MAXN]; bool mp[MAXN][MAXN]; bool dp[MAXN][MAXN][2]; int gcd(int x, int y) { if (x == 0 || y == 0) return x + y; else return gcd(y, x % y); } int main() { read(n); for (int i = 1; i <= n; i++) read(a[i]); for (int i = 0; i <= n + 1; i++) for (int j = 0; j <= n + 1; j++) mp[i][j] = gcd(a[i], a[j]) >= 2; for (int i = 0; i <= n; i++) dp[i + 1][i][0] = dp[i + 1][i][1] = true; for (int len = 1; len <= n; len++) { for (int i = 1, j = len; j <= n; i++, j++) { for (int k = i; k <= j; k++) { dp[i][j][0] |= mp[i - 1][k] && dp[i][k - 1][1] && dp[k + 1][j][0]; dp[i][j][1] |= mp[j + 1][k] && dp[i][k - 1][1] && dp[k + 1][j][0]; } } } if (dp[1][n][0]) printf("Yes\n"); else printf("No\n"); return 0; }
**【E】**Colored Cubes
【思路要点】
- 本题主要的思想在于 M e e t I n T h e M i d d l e MeetInTheMiddle MeetInTheMiddle。
- 我们可以将初始状态通过一种方式移动到一个比较有规律的状态(例如所有棋子都在对角线上,或者所有棋子都在最左边一排)。
- 同样的,我们可以将目标状态通过一种方式移动到一个比较有规律的状态,这个操作序列倒过来就是从一个比较有规律的状态移动到目标状态的方式。
- 剩余的工作就在于衔接两个比较有规律的状态了。
- 下面的代码实现了将初始状态移动到第一行,将目标状态移动到最后一行,再将两个状态进行衔接。
- 时间复杂度 O ( N 2 ) O(N^2) O(N2),操作次数 O ( 4 ∗ N 2 ) O(4*N^2) O(4∗N2)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 55; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } struct point {int x, y; }; point a[MAXN], b[MAXN]; int n, m, homea[MAXN], homeb[MAXN]; bool vis[MAXN]; vector <point> ansx, ansy, bnsx, bnsy; void getans() { memset(vis, 0, sizeof(vis)); for (int i = 1; i <= m; i++) { //pos -> (1, i); int pos = 0, val = 1e9; for (int j = 1; j <= m; j++) if (!vis[j]) { int tmp = abs(a[j].x - 1) + abs(a[j].y - i); if (tmp < val) { val = tmp; pos = j; } } vis[pos] = true; homea[pos] = i; while (a[pos].y < i) { ansx.push_back(a[pos]); a[pos].y++; ansy.push_back(a[pos]); } while (a[pos].y > i) { ansx.push_back(a[pos]); a[pos].y--; ansy.push_back(a[pos]); } while (a[pos].x > 1) { ansx.push_back(a[pos]); a[pos].x--; ansy.push_back(a[pos]); } } } void getbns() { memset(vis, 0, sizeof(vis)); for (int i = 1; i <= m; i++) { //pos -> (1, n); int pos = 0, val = 1e9; for (int j = 1; j <= m; j++) if (!vis[j]) { int tmp = abs(b[j].x - n) + abs(b[j].y - i); if (tmp < val) { val = tmp; pos = j; } } vis[pos] = true; homeb[pos] = i; while (b[pos].y < i) { bnsy.push_back(b[pos]); b[pos].y++; bnsx.push_back(b[pos]); } while (b[pos].y > i) { bnsy.push_back(b[pos]); b[pos].y--; bnsx.push_back(b[pos]); } while (b[pos].x < n) { bnsy.push_back(b[pos]); b[pos].x++; bnsx.push_back(b[pos]); } } } int main() { read(n), read(m); for (int i = 1; i <= m; i++) read(a[i].x), read(a[i].y); for (int i = 1; i <= m; i++) read(b[i].x), read(b[i].y); if (n == 1) { printf("0\n"); return 0; } getans(); getbns(); if (n == 2 && homea[1] != homeb[1]) { ansx.push_back((point) {1, 1}); ansy.push_back((point) {2, 1}); ansx.push_back((point) {2, 1}); ansy.push_back((point) {2, 2}); ansx.push_back((point) {1, 2}); ansy.push_back((point) {1, 1}); ansx.push_back((point) {1, 1}); ansy.push_back((point) {2, 1}); } else { for (int i = 1; i <= m; i++) { ansx.push_back(a[i]); a[i].x++; ansy.push_back(a[i]); while (a[i].y < b[i].y) { ansx.push_back(a[i]); a[i].y++; ansy.push_back(a[i]); } while (a[i].y > b[i].y) { ansx.push_back(a[i]); a[i].y--; ansy.push_back(a[i]); } while (a[i].x < n) { ansx.push_back(a[i]); a[i].x++; ansy.push_back(a[i]); } } } writeln(ansx.size() + bnsx.size()); for (unsigned i = 0; i < ansx.size(); i++) printf("%d %d %d %d\n", ansx[i].x, ansx[i].y, ansy[i].x, ansy[i].y); reverse(bnsx.begin(), bnsx.end()); reverse(bnsy.begin(), bnsy.end()); for (unsigned i = 0; i < bnsx.size(); i++) printf("%d %d %d %d\n", bnsx[i].x, bnsx[i].y, bnsy[i].x, bnsy[i].y); return 0; }
**【F】**Disjoint Triangles
【思路要点】
- 在题目限制的三点不共线条件下,一对相离的三角形间有且仅有两条内公切线。
- 考虑枚举内公切线,在左边和右边任取两点就能够形成一个合法的三角形对。
- 那么只需要按照两点之间向量的极角序枚举点对(公切线),并且动态维护所有点到当前直线的偏序关系即可,当我们枚举到 ( x , y ) (x,y) (x,y)时,由于题目保证了不存在三点共线, x x x和 y y y在偏序关系中一定是相邻的两个点,在枚举后交换两点的排名即可。
- 时间复杂度 O ( N 2 L o g N ) O(N^2LogN) O(N2LogN)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 2005; const int MAXM = 4e6 + 5; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } struct point {int x, y; }; point operator + (point a, point b) {return (point) {a.x + b.x, a.y + b.y}; } point operator - (point a, point b) {return (point) {a.x - b.x, a.y - b.y}; } point operator * (point a, int b) {return (point) {a.x * b, a.y * b}; } long long operator * (point a, point b) {return 1ll * a.x * b.y - 1ll * a.y * b.x; } bool operator < (point a, point b) { if (a.y == b.y) return a.x > b.x; else return a.y < b.y; } struct posi {point pos; int num; }; struct info {long double angle; int x, y; }; long long ans; int n, home[MAXN]; posi a[MAXN]; info b[MAXM]; void Swap(int x, int y) { long long tmp = home[x] - 1; long long tnp = n - home[y]; ans += tmp * (tmp - 1) / 2 * tnp * (tnp - 1) / 2; swap(home[x], home[y]); } bool cmp(posi a, posi b) { return a.pos < b.pos; } bool cnp(info a, info b) { return a.angle < b.angle; } int main() { read(n); for (int i = 1; i <= n; i++) read(a[i].pos.x), read(a[i].pos.y); sort(a + 1, a + n + 1, cmp); for (int i = 1; i <= n; i++) a[i].num = i, home[i] = i; int tot = 0; for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) if (i != j) { point tmp = a[j].pos - a[i].pos; b[++tot] = (info) {atan2l(tmp.y, tmp.x), i, j}; } sort(b + 1, b + tot + 1, cnp); for (int i = 1; i <= tot; i++) Swap(b[i].y, b[i].x); writeln(ans / 2); return 0; }
**【G】**Company Acquisitions
【思路要点】
定义一个有 k k k个白点跟随的红点 i i i的势能函数 f ( i ) = 2 k − 1 f(i)=2^k-1 f(i)=2k−1。
定义一个局面的势能为所有红点的的势能之和。
目标状态的势能为 2 n − 1 − 1 2^{n-1}-1 2n−1−1。
假设在一轮操作中,一个有 p p p个白点跟随的红点和一个有 q q q个白点跟随的红点被选中了,那么势能的期望变化量应该等于
δ = 1 2 ( ( 2 p + 1 − 1 ) − ( 2 p − 1 ) − ( 2 q − 1 ) ) + 1 2 ( ( 2 q + 1 − 1 ) − ( 2 q − 1 ) − ( 2 p − 1 ) ) = 1 \delta=\frac{1}{2}((2^{p+1}-1)-(2^p-1)-(2^q-1))+\frac{1}{2}((2^{q+1}-1)-(2^q-1)-(2^p-1))=1 δ=21((2p+1−1)−(2p−1)−(2q−1))+21((2q+1−1)−(2q−1)−(2p−1))=1
因此期望步数即为目标状态的势能减去当前状态的势能。
时间复杂度 O ( N ) O(N) O(N)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 100005; const int P = 1e9 + 7; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n, bit[MAXN], cnt[MAXN], a[MAXN]; int main() { read(n), bit[0] = 1; for (int i = 1; i <= n; i++) { bit[i] = bit[i - 1] * 2 % P; read(a[i]); if (a[i] != -1) cnt[a[i]]++; } int ans = bit[n - 1] - 1; for (int i = 1; i <= n; i++) if (a[i] == -1) ans = (ans - (bit[cnt[i]] - 1)) % P; writeln((ans + P) % P); return 0; }