【比赛链接】
【题解链接】
**【A】**String Game
【思路要点】
- 显然可以二分答案。
- 然后判定 p p p是否为删减后的 t t t的子序列即可。
- 时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN)。
【代码】
#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(""); } int n, m, a[MAXN]; char s[MAXN], t[MAXN]; bool flg[MAXN]; bool check(int mid) { memset(flg, 0, sizeof(flg)); for (int i = 1; i <= mid; i++) flg[a[i]] = true; int pos = 1; for (int i = 1; i <= m; i++) { while (pos <= n && !(!flg[pos] && s[pos] == t[i])) pos++; if (pos > n) return false; pos++; } return true; } int main() { scanf("\n%s\n%s", s + 1, t + 1); n = strlen(s + 1); m = strlen(t + 1); for (int i = 1; i <= n; i++) read(a[i]); int l = 0, r = n - m; while (l < r) { int mid = (l + r + 1) / 2; if (check(mid)) l = mid; else r = mid - 1; } writeln(l); return 0; }
**【B】**Bitwise Formula
【思路要点】
- 容易发现问题对于每一位是独立的。
- 对于每一位,枚举其取值 ( 0 / 1 ) (0/1) (0/1),模拟一遍题目中的过程,取较最优者作为答案。
- 时间复杂度 O ( N ∗ M ) O(N*M) O(N∗M)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 5005; 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, m, now[MAXN]; int x[MAXN], y[MAXN]; string name, rubbish, Min, Max; string opt[MAXN], val[MAXN]; map <string, int> num; int main() { read(n), read(m); for (int i = 1; i <= n; i++) { cin >> name; num[name] = i; cin >> rubbish; cin >> val[i]; if (val[i][0] == '0' || val[i][0] == '1') continue; else { x[i] = num[val[i]]; cin >> opt[i]; cin >> rubbish; y[i] = num[rubbish]; } } for (int j = 0; j < m; j++) { int sumz = 0, sumo = 0; now[0] = 0; for (int i = 1; i <= n; i++) { if (opt[i] == "OR") now[i] = now[x[i]] | now[y[i]]; else if (opt[i] == "AND") now[i] = now[x[i]] & now[y[i]]; else if (opt[i] == "XOR") now[i] = now[x[i]] ^ now[y[i]]; else now[i] = val[i][j] - '0'; sumz += now[i]; } now[0] = 1; for (int i = 1; i <= n; i++) { if (opt[i] == "OR") now[i] = now[x[i]] | now[y[i]]; else if (opt[i] == "AND") now[i] = now[x[i]] & now[y[i]]; else if (opt[i] == "XOR") now[i] = now[x[i]] ^ now[y[i]]; else now[i] = val[i][j] - '0'; sumo += now[i]; } if (sumz <= sumo) Min += '0'; else Min += '1'; if (sumz >= sumo) Max += '0'; else Max += '1'; } cout << Min << endl; cout << Max << endl; return 0; }
**【C】**Peterson Polyglot
【思路要点】
我们本质上希望求出的是对于每一个点 i i i,若将其所有儿子全部删除,将其孙子连接至其下并整理(合并相同的字符)后会消失的节点数 b e n i f i t i benifit_i benifiti,如果得到了 b e n i f i t i benifit_i benifiti,那么我们只需要找到 b e n i f i t i benifit_i benifiti的总和最大的一层节点,将它们的儿子删去即可。
考虑一个直观的做法:
对于每个点 i i i,首先令 b e n i f i t i benifit_i benifiti为其儿子数。
考虑整理字典树的过程,将其儿子中所有带有出边 c h ( c h = a , b , c , . . . , z ) ch(ch=a,b,c,...,z) ch(ch=a,b,c,...,z)的点找到,令这样的点数为 c n t cnt cnt,若 c n t ≥ 2 cnt≥2 cnt≥2,将 b e n i f i t i benifit_i benifiti增加 c n t − 1 cnt-1 cnt−1,并递归整理这些点的 c h ch ch出边指向的节点。
考虑其复杂度,可以发现,该算法的整理部分涉及到的总点数为 O ( ∑ b e n i f i t i ) O(\sum benifit_i) O(∑benifiti)。
并且在点 i i i处被整理的点数不会超过以点 i i i为根的子树大小减去点 i i i最大的子树的大小。
也就是说,一个点一旦在 ∑ b e n i f i t i \sum benifit_i ∑benifiti中被计算一次,计算它的子树大小就会至少翻倍。
因此,一个点在 ∑ b e n i f i t i \sum benifit_i ∑benifiti中被计算的次数为 O ( L o g N ) O(LogN) O(LogN)次,该算法的整理部分涉及到的总点数为 O ( ∑ b e n i f i t i ) = O ( N L o g N ) O(\sum benifit_i)=O(NLogN) O(∑benifiti)=O(NLogN)。
时间复杂度 O ( 26 ∗ N L o g N ) O(26*NLogN) O(26∗NLogN)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 300005; const int MAXC = 26; 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 edge {int dest; char val; }; vector <edge> a[MAXN]; vector <int> p[MAXN]; int n, benifit[MAXN]; int child[MAXN][MAXC], depth[MAXN]; int calc(int depth) { if (p[depth].size() <= 1) return 0; int ans = 0; for (int j = 0; j < 26; j++) { p[depth + 1].clear(); int cnt = 0; for (unsigned i = 0; i < p[depth].size(); i++) if (child[p[depth][i]][j]) { cnt++; p[depth + 1].push_back(child[p[depth][i]][j]); } ans += max(cnt - 1, 0) + calc(depth + 1); } return ans; } void dfs(int pos, int fa) { depth[pos] = depth[fa] + 1; for (unsigned i = 0; i < a[pos].size(); i++) if (a[pos][i].dest != fa) { child[pos][a[pos][i].val - 'a'] = a[pos][i].dest; dfs(a[pos][i].dest, pos); } } int main() { read(n); for (int i = 1; i <= n - 1; i++) { int x, y; char z; scanf("%d%d %c", &x, &y, &z); a[x].push_back((edge) {y, z}); a[y].push_back((edge) {x, z}); } dfs(1, 0); for (int i = 1; i <= n; i++) { p[0].clear(); for (int j = 0; j < MAXC; j++) if (child[i][j]) { benifit[depth[i]]++; p[0].push_back(child[i][j]); } benifit[depth[i]] += calc(0); } int ans = 1; for (int i = 1; i <= n; i++) if (benifit[i] > benifit[ans]) ans = i; writeln(n - benifit[ans]); writeln(ans); return 0; }
**【D】**Parquet Re-laying
【思路要点】
我们发现难以轻易地构造出一组无解的情况,因此我们猜想所有合法的状态都是能够相互到达的。
由于操作可逆,我们可以试图将给出的两个状态分别归一与一个统一的中间状态。
N N N和 M M M必然至少有一个是偶数,不妨令 N N N为偶数( M M M为偶数的情况是对称的),我们希望将每个状态归一于所有地板竖直摆放的状态。
考虑最左边的列,若所有地板已经竖直摆放,那么将这列删去(不予考虑)
否则,最左边的列可能包含一些水平摆放的地板的左半段,并且由于 N N N是偶数,这样的左半段的个数也为偶数,它们两两之间由竖直摆放的地板连接,相隔的距离也为偶数。
若一对竖直对齐的水平摆放的地板中间(两列)仅有竖直摆放的地板,那么显然我们可以将这些地板通过操作变为水平摆放的,再将这一段内的所有地板通过操作变为竖直摆放的,这样就消除了一对竖直对齐的水平摆放的地板,使用的操作次数是 O ( N ) O(N) O(N)的。
可以证明若当前还存在水平摆放的地板,那么一定存在上述的一对竖直对齐的水平摆放的地板,使得它们中间仅有竖直摆放的地板。证明较为显然,因为一对竖直对齐的水平摆放的地板中间若存在水平摆放的地板,一定可以找到一对距离更近的竖直对齐的水平摆放的地板。
那么算法就很明显了,暴力找到上述的一对竖直对齐的水平摆放的地板,花费 O ( N ) O(N) O(N)的操作次数消除它们,重复执行直到不存在水平摆放的地板。
执行次数至多 O ( N 2 ) O(N^2) O(N2),单次花费时间至多 O ( N 3 ) O(N^3) O(N3)。
时间复杂度 O ( N 5 ) O(N^5) O(N5),使用操作次数上限 O ( N 3 ) O(N^3) O(N3)。
【代码】
#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(""); } int n, m; char s[MAXN][MAXN]; vector <int> ansx[2], ansy[2]; void getplan(vector <int> &ansx, vector <int> &ansy) { if (n % 2 == 0) { int cnt = 0; for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) cnt += s[i][j] == 'L'; while (cnt != 0) { int px = 0, py = 0, qx = 0, qy = 0; for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) if (s[i][j] == 'L') { bool flg = false; for (int k = i + 1; k <= n; k++) if (s[k][j] == 'L') { flg = true; qx = k, qy = j; break; } else if (s[k][j] == 'R' || s[k][j + 1] == 'L') break; if (flg) px = i, py = j; } if (px == 0) { printf("Error!\n"); exit(0); } for (int i = px + 1; i <= qx - 1; i += 2) { ansx.push_back(i); ansy.push_back(py); } for (int i = px; i <= qx; i += 2) { ansx.push_back(i); ansy.push_back(py); s[i][py] = s[i][py + 1] = 'U'; s[i + 1][py] = s[i + 1][py + 1] = 'D'; } cnt -= 2; } } else { int cnt = 0; for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) cnt += s[i][j] == 'U'; while (cnt != 0) { int px = 0, py = 0, qx = 0, qy = 0; for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) if (s[i][j] == 'U') { bool flg = false; for (int k = j + 1; k <= m; k++) if (s[i][k] == 'U') { flg = true; qx = i, qy = k; break; } else if (s[i][k] == 'D' || s[i + 1][k] == 'U') break; if (flg) px = i, py = j; } if (px == 0) { printf("Error!\n"); exit(0); } for (int i = py + 1; i <= qy - 1; i += 2) { ansx.push_back(px); ansy.push_back(i); } for (int i = py; i <= qy; i += 2) { ansx.push_back(px); ansy.push_back(i); s[px][i] = s[px + 1][i] = 'L'; s[px][i + 1] = s[px + 1][i + 1] = 'R'; } cnt -= 2; } } } int main() { read(n), read(m); for (int i = 1; i <= n; i++) scanf("\n%s", s[i] + 1); getplan(ansx[0], ansy[0]); for (int i = 1; i <= n; i++) scanf("\n%s", s[i] + 1); getplan(ansx[1], ansy[1]); writeln(ansx[0].size() + ansx[1].size()); for (unsigned i = 0; i < ansx[0].size(); i++) printf("%d %d\n", ansx[0][i], ansy[0][i]); reverse(ansx[1].begin(), ansx[1].end()); reverse(ansy[1].begin(), ansy[1].end()); for (unsigned i = 0; i < ansx[1].size(); i++) printf("%d %d\n", ansx[1][i], ansy[1][i]); return 0; }
**【E】**Selling Numbers
【思路要点】
- 由于要考虑进位的问题,我们考虑从低位向高位DP。
- 我们发现无论如何,我们加上的数是对于每个 B i B_i Bi相同的,因此 B i B_i Bi相对的大小关系不会产生变化。
- 那么,无论我们加上什么数,从第 i i i位向第 i + 1 i+1 i+1位有进位的数一定是第 i i i位到第一位组成的数较大的一些数,因此,对于每一位,可能的产生进位的集合只有 O ( N ) O(N) O(N)个。
- 记 d p i , j dp_{i,j} dpi,j表示已经考虑了所有数较低的 i i i位,其中有 j j j个数产生了进位时,能够获得的最大价值。
- 记 d p i , ∗ dp_{i,*} dpi,∗为一层,从 d p i , ∗ dp_{i,*} dpi,∗到 d p i + 1 , ∗ dp_{i+1,*} dpi+1,∗之间的转移我们可以较为简单地做到 O ( 10 ∗ N ) O(10*N) O(10∗N)的时间复杂度,完成一层的转移后,对所有数第 i + 1 i+1 i+1位到第一位组成的数重新排序即可。
- 时间复杂度 O ( N ∗ M a x { ∣ A ∣ , ∣ B i ∣ } ∗ ( L o g N + 10 ) ) O(N*Max\{|A|,|B_i|\}*(LogN+10)) O(N∗Max{∣A∣,∣Bi∣}∗(LogN+10))。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 1005; const int INF = 1e9; 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, m, Max, type, val[MAXN], len[MAXN], home[MAXN]; int a[MAXN][MAXN], b[MAXN], dp[MAXN][MAXN]; char s[MAXN]; bool cmp(int x, int y) { if (a[x][type] == a[y][type]) return home[x] < home[y]; else return a[x][type] > a[y][type]; } int main() { scanf("%s", s + 1), read(n); m = strlen(s + 1); reverse(s + 1, s + m + 1); for (int i = 1; i <= m; i++) if (s[i] == '?') b[i] = -1; else b[i] = s[i] - '0'; int Max = m; for (int i = 1; i <= n; i++) { scanf("\n%s", s + 1); len[i] = strlen(s + 1); reverse(s + 1, s + len[i] + 1); for (int j = 1; j <= len[i]; j++) a[i][j] = s[j] - '0'; chkmax(len[i], m); chkmax(Max, len[i]); } Max++; for (int i = 0; i <= 9; i++) read(val[i]); for (int i = 0; i <= Max; i++) for (int j = 0; j <= n; j++) dp[i][j] = -INF; dp[0][0] = 0; static int now[MAXN]; for (int i = 1; i <= n; i++) now[i] = i; for (int i = 1; i <= Max; i++) { int from, to; if (b[i] == -1) { if (i == m) from = 1, to = 9; else from = 0, to = 9; } else from = to = b[i]; for (int ch = from; ch <= to; ch++) { int sum = 0, cnt = 0; for (int j = 1; j <= n; j++) if (i <= len[j]) { sum += val[(a[j][i] + ch) % 10]; if (a[j][i] + ch >= 10) cnt++; } chkmax(dp[i][cnt], dp[i - 1][0] + sum); for (int j = 1; j <= n; j++) { if (i <= len[now[j]]) { sum -= val[(a[now[j]][i] + ch) % 10]; if (a[now[j]][i] + ch >= 10) cnt--; } sum += val[(a[now[j]][i] + ch + 1) % 10]; if (a[now[j]][i] + ch + 1 >= 10) cnt++; chkmax(dp[i][cnt], dp[i - 1][j] + sum); } } for (int j = 1; j <= n; j++) home[now[j]] = j; type = i; sort(now + 1, now + n + 1, cmp); } writeln(dp[Max][0]); return 0; }