【比赛链接】
【题解链接】
【Div.2 A】Olympiad
【思路要点】
- 答案为原数集中除去所有0以外数的种类数,哈希计数即可。
- 时间复杂度\(O(N+Max\{a_i\})\)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 1005; 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(""); } bool a[MAXN]; int main() { int n; read(n); for (int i = 1; i <= n; i++) { int x; read(x); a[x] = true; } int ans = 0; for (int i = 1; i < MAXN; i++) ans += a[i]; writeln(ans); return 0; }
【Div.2 B】Vile Grasshoppers
【思路要点】
- 显然,如果我们找到一个在\([P+1,Y]\)中的质数,那么这个质数一定是一个合法的答案。
- \(10^9\)以内相邻的两个质数最大相差300,因此,我们只需要从\(Y\)开始向下枚举答案即可。
- 时间复杂度\(O(300*\sqrt{Y})\)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 100005; 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 main() { int n, m; read(n), read(m); for (int i = m; i >= n + 1; i--) { bool flg = true; for (int j = 2; j <= n && j * j <= i; j++) if (i % j == 0) { flg = false; break; } if (flg) { printf("%d\n", i); return 0; } } printf("-1\n"); return 0; }
【Div.2 C/Div.1 A】Save Energy!
【思路要点】
- 不难发现问题中的过程具有周期性,根据周期的信息直接计算答案即可,计算方式详见代码。
- 时间复杂度\(O(1)\)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 100005; 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 main() { long long k, d, t; read(k), read(d), read(t); t = t * 2; long long c = (k - 1) / d + 1; long long len = c * d; long long cnt = k * 2 + (len - k); long long b = t / cnt; long long ans = b * len; t %= cnt; if (t <= k * 2) { ans += t / 2; if (t % 2 == 0) printf("%I64d\n", ans); else printf("%I64d.5\n", ans); } else { t -= k * 2; ans += k; ans += t; printf("%I64d\n", ans); } return 0; }
【Div.2 D/Div.1 B】Sleepy Game
【思路要点】
- 显然,如果图中存在一条起点为\(s\),终点为一个出度为0的点,且长度为奇数的路径,那么答案为Win。
- 否则,如果图中存在一个\(s\)可以到达的环,答案为Draw,否则答案为Lose。
- 上面的两点分别可以用反向建边+BFS和DFS来进行判断并且求得一个方案。
- 时间复杂度\(O(N+M)\)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 200005; 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 l, r, q[MAXN], t[MAXN]; vector <int> a[MAXN], b[MAXN]; int n, m, d[MAXN], sodd[MAXN], seven[MAXN]; bool found, vis[MAXN], ins[MAXN], odd[MAXN], even[MAXN]; void work(int pos) { if (ins[pos]) { found = true; return; } if (vis[pos]) return; vis[pos] = ins[pos] = true; for (unsigned i = 0; i < b[pos].size(); i++) work(b[pos][i]); ins[pos] = false; } int main() { read(n), read(m); for (int i = 1; i <= n; i++) { int cnt; read(cnt); d[i] = cnt; while (cnt--) { int x; read(x); a[x].push_back(i); b[i].push_back(x); } } l = 0, r = -1; for (int i = 1; i <= n; i++) if (d[i] == 0) { r++; t[r] = 0; q[r] = i; even[i] = true; seven[i] = 1; } while (l <= r) { int tmp = q[l], type = t[l++]; if (type) { for (unsigned i = 0; i < a[tmp].size(); i++) if (!even[a[tmp][i]]) { r++; t[r] = 0; q[r] = a[tmp][i]; even[a[tmp][i]] = true; seven[a[tmp][i]] = sodd[tmp] + 1; } } else { for (unsigned i = 0; i < a[tmp].size(); i++) if (!odd[a[tmp][i]]) { r++; t[r] = 1; q[r] = a[tmp][i]; odd[a[tmp][i]] = true; sodd[a[tmp][i]] = seven[tmp] + 1; } } } int pos; read(pos); if (odd[pos]) { printf("Win\n"); printf("%d ", pos); while (true) { for (unsigned i = 0; i < b[pos].size(); i++) if (even[b[pos][i]] && seven[b[pos][i]] == sodd[pos] - 1) { pos = b[pos][i]; break; } printf("%d ", pos); if (b[pos].size() == 0) return 0; for (unsigned i = 0; i < b[pos].size(); i++) if (odd[b[pos][i]] && sodd[b[pos][i]] == seven[pos] - 1) { pos = b[pos][i]; break; } printf("%d ", pos); } } work(pos); if (found) printf("Draw\n"); else printf("Lose\n"); return 0; }
【Div.2 E/Div.1 C】Lock Puzzle
【思路要点】
- 首先,如果\(S\)和\(T\)的字符组成不一样,答案显然是无解。
- 否则,我们可以把问题转化成:“给定一个\(N\)的排列,用题目所给的操作排序它”。
- 令\(A\)表示\([L+1,R-1]\)之间的所有数从大到小排列形成的一段数,\(A^{R}\)表示\(A\)的倒序,下面是一种可能的将\(L\)和\(R\)加入\(A\)中并保持有序的方法。
- \(...A.....L.....\)
- \(........A.....L\)
- \(L........A.....\)
- \(A^{R}L.............\)
- 可以看到,经过上面的3次操作,我们成功地将\(L\)加在了\(L+1\)的旁边,并将\(A\)倒了序。
- \(...A^{R}.....R.....\)
- \(........A^{R}.....R\)
- \(R........A^{R}.....\)
- \(AR.............\)
- 类似地,经过上面的3次操作,我们成功地将\(R\)加在了\(R-1\)的旁边,并将\(A\)倒了序。
- 初始时,令\(A\)中包含\(\lfloor\frac{N+1}{2}\rfloor\),成对地加入数字即可。
- 时间复杂度\(O(N^2)\),使用变换次数\(3N-2\)或\(3N-1\)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 2005; const int MAXANS = 7005; 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], t[MAXN]; vector <int> a[MAXN], b[MAXN]; int n, cnt, ans[MAXANS], val[MAXN]; int vl, vr, l, r; int find(int x) { for (int i = 1; i <= n; i++) if (val[i] == x) return i; return -1; } void operate(int x) { ans[++cnt] = x; static int tmp[MAXN]; int len = 0; for (int i = 1; i <= x; i++) tmp[++len] = val[n - i + 1]; for (int i = 1; i <= n - x; i++) tmp[++len] = val[i]; for (int i = 1; i <= n; i++) { val[i] = tmp[i]; if (val[i] == vl) l = i; if (val[i] == vr) r = i; } } int main() { read(n); scanf("\n%s\n%s", s + 1, t + 1); for (int i = 1; i <= n; i++) { a[s[i] - 'a'].push_back(i); b[t[i] - 'a'].push_back(i); } for (int i = 0; i <= 25; i++) if (a[i].size() != b[i].size()) { printf("-1\n"); return 0; } else { for (unsigned j = 0; j < a[i].size(); j++) val[a[i][j]] = b[i][j]; } if (n == 1) { printf("0\n"); return 0; } vl = (n + 1) / 2, vr = (n + 1) / 2; l = find(vl), r = find(vr); while (vl != 1 || vr != n) { if (r >= l) { if (r != n) operate(n - r); int tmp = find(vr + 1); operate(n - tmp); vr++; operate(1); } else { if (l != n) operate(n - l); int tmp = find(vl - 1); operate(n - tmp); vl--; operate(1); } } if (l >= r) operate(n); writeln(cnt); for (int i = 1; i <= cnt; i++) printf("%d ", ans[i]); return 0; }
【Div.1 D】World of Tank
【思路要点】
- 坦克每走\(T\)步,可以开一炮,如果坦克在同一行行驶,那么我们认为步数可以无限累加,但如果坦克要换行,那么步数需要与\(T\)取最小值。
- 如此来看这个问题,我们能够轻松地得到一个\(O(N)\)的DP。
- 记\(F_{i,j}\)表示坦克在\((i,j)\)可能积累的最大步数,如果不能到达,记-1。
- 转移分为向前与换行,都比较显然。
- 但实际上,我们可以认为可能换行的位置只有每一个有方块的坐标的后面一位,因为否则我们一定可以将方案转变为满足这个限制而不会使方案变得不合法。
- 所以,把这些坐标作为“关键坐标”,在这些坐标上DP即可。
- 时间复杂度\(O(M_1+M_2)\)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 2000005; 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, m1, m2, m, t; int x1[MAXN], x2[MAXN]; int mid1[MAXN], mid2[MAXN]; int pos1[MAXN], pos2[MAXN]; int dp[3][MAXN], path[3][MAXN], pos[MAXN]; int main() { read(n), read(m1), read(m2), read(t); for (int i = 1; i <= m1; i++) read(x1[i]); for (int i = 1; i <= m2; i++) read(x2[i]); pos[++m] = 0; int p1 = 1, p2 = 1; while (p1 <= m1 || p2 <= m2) { if (p1 > m1) pos[++m] = x2[p2++] + 1; else if (p2 > m2) pos[++m] = x1[p1++] + 1; else { int Min = min(x1[p1], x2[p2]); pos[++m] = Min + 1; if (Min == x1[p1]) p1++; if (Min == x2[p2]) p2++; } } if (pos[m] != n + 1) pos[++m] = n + 1; p1 = 1; for (int i = 1; i <= m1; i++) { while (x1[i] > pos[p1]) p1++; if (pos[p1] > x1[i]) mid1[p1] = x1[i]; pos1[p1] += x1[i] == pos[p1]; } p2 = 1; for (int i = 1; i <= m2; i++) { while (x2[i] > pos[p2]) p2++; if (pos[p2] > x2[i]) mid2[p2] = x2[i]; pos2[p2] += x2[i] == pos[p2]; } memset(dp, -1, sizeof(dp)); dp[1][1] = 0; for (int i = 1; i <= m - 1; i++) { if (dp[1][i] != -1 && pos2[i] == 0) { int tmp = min(dp[1][i], t); if (tmp > dp[2][i]) { dp[2][i] = tmp; path[2][i] = 1; } } if (dp[2][i] != -1 && pos1[i] == 0) { int tmp = min(dp[2][i], t); if (tmp > dp[1][i]) { dp[1][i] = tmp; path[1][i] = 1; } } if (dp[1][i] != -1) { int tmp = dp[1][i]; if (pos1[i + 1] == 0 && mid1[i + 1] == 0) tmp += pos[i + 1] - pos[i]; if (pos1[i + 1] == 0 && mid1[i + 1] != 0) { tmp += mid1[i + 1] - pos[i] - 1; if (tmp < t) tmp = -1; else tmp -= t; if (tmp != -1) tmp += pos[i + 1] - mid1[i + 1] + 1; } if (pos1[i + 1] != 0 && mid1[i + 1] == 0) { tmp += pos[i + 1] - pos[i] - 1; if (tmp < t) tmp = -1; else tmp -= t; if (tmp != -1) tmp += 1; } if (pos1[i + 1] != 0 && mid1[i + 1] != 0) { tmp += mid1[i + 1] - pos[i] - 1; if (tmp < t) tmp = -1; else tmp -= t; if (tmp != -1) tmp += pos[i + 1] - mid1[i + 1]; if (tmp < t) tmp = -1; else tmp -= t; if (tmp != -1) tmp += 1; } dp[1][i + 1] = tmp; } if (dp[2][i] != -1) { int tmp = dp[2][i]; if (pos2[i + 1] == 0 && mid2[i + 1] == 0) tmp += pos[i + 1] - pos[i]; if (pos2[i + 1] == 0 && mid2[i + 1] != 0) { tmp += mid2[i + 1] - pos[i] - 1; if (tmp < t) tmp = -1; else tmp -= t; if (tmp != -1) tmp += pos[i + 1] - mid2[i + 1] + 1; } if (pos2[i + 1] != 0 && mid2[i + 1] == 0) { tmp += pos[i + 1] - pos[i] - 1; if (tmp < t) tmp = -1; else tmp -= t; if (tmp != -1) tmp += 1; } if (pos2[i + 1] != 0 && mid2[i + 1] != 0) { tmp += mid2[i + 1] - pos[i] - 1; if (tmp < t) tmp = -1; else tmp -= t; if (tmp != -1) tmp += pos[i + 1] - mid2[i + 1]; if (tmp < t) tmp = -1; else tmp -= t; if (tmp != -1) tmp += 1; } dp[2][i + 1] = tmp; } } if (dp[1][m] == -1 && dp[2][m] == -1) { printf("No\n"); return 0; } printf("Yes\n"); int px, py; if (dp[1][m] == -1) py = 2, px = m; else py = 1, px = m; static int shifts[MAXN], cnts = 0; static int x[MAXN], y[MAXN], cnt = 0; int now = 0; while (px != 1 || py != 1) { if (path[py][px]) { cnt += now; int tx = pos[px] - dp[py][px]; for (int i = 1; i <= now; i++) { tx += t; x[cnt - i + 1] = tx; y[cnt - i + 1] = py; } py = 3 - py; now = 0; shifts[++cnts] = pos[px]; } else { if (py == 1) now += mid1[px] != 0, now += pos1[px] != 0; else now += mid2[px] != 0, now += pos2[px] != 0; px = px - 1; } } cnt += now; int tx = 0; for (int i = 1; i <= now; i++) { tx += t; x[cnt - i + 1] = tx; y[cnt - i + 1] = py; } writeln(cnts); reverse(shifts + 1, shifts + cnts + 1); for (int i = 1; i <= cnts; i++) printf("%d ", shifts[i]); printf("\n"); writeln(cnt); reverse(x + 1, x + cnt + 1); reverse(y + 1, y + cnt + 1); for (int i = 1; i <= cnt; i++) printf("%d %d\n", x[i], y[i]); return 0; }
【Div.1 E】Iqea
【思路要点】
- 如下图,我们将同一横坐标的连续的一些点看作是一整个点,那么将会形成一棵树。
- 任意两个点之间的最短路一定会经过它们在树上对应路径上的每一个点的至少一部分。
- 考虑树分治。如图,假设我们需要计算\((x_a,y_a)\)与\((x_b,y_b)\)之间经过\(v_c\)的最短路。
- 令\((x_a,y_a)\)到\(v_c\)上最近的距离为\(d_a\),且到达的点在\(v_c\)上纵坐标为\(z_a\),\((x_b,y_b)\)到\(v_c\)上最近的距离为\(d_b\),且到达的点在\(v_c\)上纵坐标为\(z_b\)。
- 那么\((x_a,y_a)\)与\((x_b,y_b)\)之间的最短路的长度应当为\(d_a+d_b+|z_a-z_b|\)。
- 在每个分治重心用树状数组维护信息并支持询问即可。
- 时间复杂度\(O(NLogN+QLog^2N)\)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 300005; const int MAXLOG = 20; const int INF = 1e9; 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 info {int d, y; }; struct segment {int x, l, r; }; struct point {int x, y, belong; }; void chkmax(int &x, int y) {x = max(x, y); } void chkmin(int &x, int y) {x = min(x, y); } struct BinaryIndexTree { int n, pl, pr; vector <int> pre, suf; void init(int l, int r) { n = r - l + 1; pl = l, pr = r; for (int i = 0; i <= n; i++) { pre.push_back(INF); suf.push_back(INF); } } void modify(info x) { int pos = x.y - pl + 1, val = x.d - x.y; for (int i = pos; i <= n; i += i & -i) chkmin(pre[i], val); pos = pr - x.y + 1, val = x.d + x.y; for (int i = pos; i <= n; i += i & -i) chkmin(suf[i], val); } int query(info x) { int ans = INF; for (int i = x.y - pl + 1; i != 0; i -= i & -i) chkmin(ans, pre[i] + x.y); for (int i = pr - x.y + 1; i != 0; i -= i & -i) chkmin(ans, suf[i] - x.y); return ans + x.d; } } bit[MAXN]; bool vis[MAXN], used[MAXN]; int n, m, root, Root; int size[MAXN], weight[MAXN]; int father[MAXN], depth[MAXN]; point a[MAXN]; segment b[MAXN]; info c[MAXN][MAXLOG]; map <int, int> index[MAXN]; map <int, bool> exist[MAXN]; vector <int> t[MAXN], e[MAXN], cx[MAXN], cy[MAXN]; bool cmpx(int x, int y) {return a[x].x < a[y].x; } bool cmpy(int x, int y) {return a[x].y < a[y].y; } void getroot(int pos, int fa, int tot) { size[pos] = 1; weight[pos] = 0; for (unsigned i = 0; i < t[pos].size(); i++) if (t[pos][i] != fa && !vis[t[pos][i]]) { getroot(t[pos][i], pos, tot); size[pos] += size[t[pos][i]]; chkmax(weight[pos], size[t[pos][i]]); } chkmax(weight[pos], tot - size[pos]); if (weight[pos] < weight[root]) root = pos; } void gsize(int pos, int fa) { size[pos] = 1; for (unsigned i = 0; i < t[pos].size(); i++) if (t[pos][i] != fa && !vis[t[pos][i]]) { gsize(t[pos][i], pos); size[pos] += size[t[pos][i]]; } } void work(int pos, int dep, int fa) { depth[pos] = dep; father[pos] = fa; vis[pos] = true; bit[pos].init(b[pos].l, b[pos].r); static int q[MAXN]; int l = 0, r = -1; for (int i = b[pos].l; i <= b[pos].r; i++) { int tmp = index[b[pos].x][i]; used[tmp] = true; q[++r] = tmp; c[tmp][dep] = (info) {0, i}; } while (l <= r) { int tmp = q[l++]; for (unsigned i = 0; i < e[tmp].size(); i++) if (!used[e[tmp][i]] && c[e[tmp][i]][dep].d == 0) { c[e[tmp][i]][dep] = (info) {c[tmp][dep].d + 1, c[tmp][dep].y}; q[++r] = e[tmp][i]; } } gsize(pos, 0); for (unsigned i = 0; i < t[pos].size(); i++) if (!vis[t[pos][i]]) { root = 0; getroot(t[pos][i], 0, size[t[pos][i]]); work(root, dep + 1, pos); } } int main() { read(n); for (int i = 1; i <= n; i++) { read(a[i].x), read(a[i].y); index[a[i].x][a[i].y] = i; cx[a[i].x].push_back(i); cy[a[i].y].push_back(i); } for (int i = 1; i < MAXN; i++) { sort(cx[i].begin(), cx[i].end(), cmpy); for (unsigned j = 0, last = 0; j < cx[i].size(); j++) { if (j != cx[i].size() - 1 && a[cx[i][j]].y + 1 == a[cx[i][j + 1]].y) { e[cx[i][j]].push_back(cx[i][j + 1]); e[cx[i][j + 1]].push_back(cx[i][j]); continue; } b[++m] = (segment) {i, a[cx[i][last]].y, a[cx[i][j]].y}; for (unsigned k = last; k <= j; k++) a[cx[i][k]].belong = m; last = j + 1; } } for (int i = 1; i < MAXN; i++) { sort(cy[i].begin(), cy[i].end(), cmpx); for (unsigned j = 0; j < cy[i].size(); j++) if (j != cy[i].size() - 1 && a[cy[i][j]].x + 1 == a[cy[i][j + 1]].x) { e[cy[i][j]].push_back(cy[i][j + 1]); e[cy[i][j + 1]].push_back(cy[i][j]); int x = a[cy[i][j]].belong, y = a[cy[i][j + 1]].belong; if (!exist[x][y]) { exist[x][y] = exist[y][x] = true; t[x].push_back(y); t[y].push_back(x); } } } weight[root = 0] = m + 1; getroot(1, 0, m); work(Root = root, 1, 0); int q; read(q); while (q--) { int opt, x, y; read(opt), read(x), read(y); int id = index[x][y], p = a[id].belong; if (opt == 1) { while (p) { bit[p].modify(c[id][depth[p]]); p = father[p]; } } else { int ans = INF; while (p) { chkmin(ans, bit[p].query(c[id][depth[p]])); p = father[p]; } if (ans > n) writeln(-1); else writeln(ans); } } return 0; }