2021牛客OI赛前集训营-普及组题解(第四场)
Problem A. 多国语言
做法
设 ,
这两个数组可以扫描一遍 数组得到。
记 ,
如果 ,输出 ">-<";
否则如果 ,输出 "^v^";
否则只存在唯一的 ,满足
,如果此时
,则输出
,否则输出 "^v^"。
时间复杂度 。
代码
#include <bits/stdc++.h> using namespace std; const int maxn = 100100; int cnt[maxn], cntb[maxn], a[maxn]; int main(void) { // freopen("sample.in", "r", stdin); int T; scanf("%d", &T); assert(1 <= T && T <= 10); while (T--) { int n, m; scanf("%d%d", &n, &m); assert(1 <= n && n <= 100000); assert(1 <= m && m <= 100000); for (int i=1; i<=m; i++) cnt[i] = cntb[i] = 0; for (int i=1; i<=n; i++) { scanf("%d", &a[i]); assert(1 <= a[i] && a[i] <= m); cnt[a[i]]++; } for (int i=1; i<=n; i++) { int b; scanf("%d", &b); assert(b == 0 || b == 1); if (b == 1) cntb[a[i]]++; } int num = 0, idx = 0; for (int i=1; i<=m; i++) if (cntb[i]) { num++; if (cntb[i] == cnt[i]) idx = i; } if (num == 1 && idx) { printf("%d\n", idx); } else if (num) { printf("^v^\n"); } else { printf(">-<\n"); } } return 0; }
Problem B. double u
做法
将 中所有 'w' 替换为 "uu",所有 'm' 替换为 "nn",假设替换后长度为
,若
,则贪心的选择最靠前的一个 "uu"/"nn" 子串替换为 'w'/'m',这样可以使得
减少1,一直替换直到
。由于题意保证存在答案,则这样贪心一定能得到答案。
时间复杂度 。
代码
#include <bits/stdc++.h> using namespace std; const int maxn = 200200; char s[maxn], t[maxn], u[maxn]; int main(void) { // freopen("sample.in", "r", stdin); int T; scanf("%d", &T); assert(1 <= T && T <= 10); while (T--) { int n; scanf("%d", &n); assert(1 <= n && n <= 100000); scanf("%s", t); int m = strlen(t); assert(1 <= m && m <= 100000); int l = 0; for (int i=0; i<m; i++) { if (t[i] == 'w') u[l++] = 'u', u[l++] = 'u'; else if (t[i] == 'm') u[l++] = 'n', u[l++] = 'n'; else u[l++] = t[i]; } assert(l >= n); int d = l - n; for (int i=0, j=0; i<n; i++) { if (d) { if (u[j] == 'u' && u[j+1] == 'u') { s[i] = 'w'; j += 2; d--; } else if (u[j] == 'n' && u[j+1] == 'n') { s[i] = 'm'; j += 2; d--; } else s[i] = u[j++]; } else s[i] = u[j++]; } assert(d == 0); s[n] = 0; printf("%s\n", s); } return 0; }
Problem C. 活动
做法
可以观察到,放一个物品必然使得篮子内重量加倍,因此最多只能放 件物品。考虑动态规划,
篮子内放
件物品,当前重量为
的方案数,则
不妨假设 同阶,那么直接求这个dp的复杂度为
。
考虑到这个转移最多是一个连续的区间 的和减去一个数,维护前缀和即可将转移复杂度优化到
。
时间复杂度 。
代码
#include <bits/stdc++.h> using namespace std; const int mod = 998244353; const int maxn = 100100; int f[21][maxn], sum[21][maxn]; int main(void) { // freopen("sample.in", "r", stdin); int n, x, y; scanf("%d%d%d", &n, &x, &y); assert(1 <= n && n <= 100000); assert(1 <= x && x <= 100000); assert(1 <= y && y <= n); f[0][0] = 1; for (int i=0; i<=x; i++) sum[0][i] = 1; for (int i=1; i<=20; i++) { for (int j=1; j<=x; j++) { f[i][j] = sum[i-1][j/2]; if (j > n) f[i][j] = (f[i][j] - sum[i-1][j-n-1] + mod) % mod; if (j-j/2 <= y && y <= j) f[i][j] = (f[i][j] - f[i-1][j-y] + mod) % mod; sum[i][j] = (sum[i][j-1] + f[i][j]) % mod; } } int ans = 0; for (int i=0; i<=20; i++) ans = (ans + f[i][x]) % mod; printf("%d\n", ans); return 0; }
Problem D. 迷宫
做法
将网络中每个格子到它的下一个格子连一条有向边,得到一个具有特殊性质的图。考虑在图上进行动态规划,具体来说,令 左/右脚在结点
时得到的最大答案,则
。(
是
指向的下一个结点。)
但这个图是可能存在环的,这意味着动态规划具有后效性。可以发现环上的结点数必然是偶数个。这是因为一个环内指向上的结点必然要和指向下的结点一样多,指向左的结点要和指向右的结点一样多。那么在环上,如果一开始以左脚踏入结点 ,则永远以左脚踏入结点
,右脚同理。由于每次走完后,结点的权值会变成自己的相反数,因此走两圈相当于没走。
由此可以得到一个暴力,即从每个点开始一直走,直到某个点需要访问三次则停下。假设 同阶,时间复杂度:
。
上述做法复杂度的瓶颈在于求环上每个点的答案。考虑到从环上的结点 出发,环的长度为
,走第一圈时相当于走了一个从
出发的长度
的区间,走第二圈时相当于走了一个到
结束的长度
的区间(
是
在环上的上一个结点)。这两部分的求解是完全相同的,可以把区间和拆成两个前缀和的差,然后求前缀和的一个环上的滑窗max,可以用单调队列做到
。因此最终时间复杂度为
。
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; using ull = unsigned long long; const ll B = (ll)1e15; const ll C = (ll)2e15 + 21; const int maxn = 1010; char d[maxn]; int val[maxn * maxn], nex[maxn * maxn]; int vis[maxn * maxn], st[maxn * maxn], top, in_st[maxn * maxn], is_r[maxn * maxn]; vector <int> e[maxn * maxn]; ll f[maxn * maxn][2], g[maxn * maxn]; void Max(ll & x, ll y) { if (y > x) x = y; } void ring_max(vector <int> & w) { static ll sum[maxn*maxn*2]; static int que[maxn*maxn*2]; int n = w.size(); sum[0] = 0; for (int i=1; i<2*n; i++) sum[i] = sum[i-1] + w[(i-1<n)?(i-1):(i-1-n)]; // g[l] = max(sum[r] - sum[l]), l+1 <= r <= l+n int head = 0, tail = 0; for (int i=0; i<2*n; i++) { while (tail < head && sum[que[head-1]] < sum[i]) head--; que[head++] = i; if (i-que[tail] >= n) tail++; if (i >= n) g[i-n] = sum[que[tail]] - sum[i-n]; } } void fnd_ring(int u) { if (in_st[u]) { vector <int> a; for (int i=in_st[u]; i<=top; i++) { is_r[st[i]] = 1; a.push_back(st[i]); } // calc f[a[i]][0 / 1] int m = a.size(); vector <int> w(m); for (int u=0; u<2; u++) { for (int i=0; i<m; i++) { if (i % 2 == u) w[i] = val[a[i]]; else w[i] = -val[a[i]]; } ring_max(w); for (int i=0; i<m; i++) { if (i % 2 == 0) Max(f[a[i]][u], g[i]); else Max(f[a[i]][!u], g[i]); } reverse(w.begin(), w.end()); ring_max(w); for (int i=0; i<m; i++) { if (i % 2 == 0) Max(f[a[(m-i)%m]][u], g[i]); else Max(f[a[(m-i)%m]][!u], g[i]); } } for (int i=0; i<m; i++) Max(f[a[i]][0], 0); for (int i=0; i<m; i++) Max(f[a[i]][1], 0); return ; } if (vis[u] || nex[u] == -1) return ; vis[u] = 1; st[++top] = u; in_st[u] = top; fnd_ring(nex[u]); top--; in_st[u] = 0; } void dp(int u) { for (auto v : e[u]) { if (is_r[v]) continue; f[v][0] = val[v] + max(0ll, f[u][1]); f[v][1] = -val[v] + max(0ll, f[u][0]); dp(v); } } int main(void) { // freopen("sample.in", "r", stdin); int n, m; scanf("%d%d", &n, &m); assert(1 <= n && n <= 1000); assert(1 <= m && m <= 1000); for (int i=0; i<n; i++) { scanf("%s", d); assert(strlen(d) == m); for (int j=0; j<m; j++) { assert(d[j] == '^' || d[j] == 'v' || d[j] == '<' || d[j] == '>'); int ni, nj; if (d[j] == '^') ni = i-1, nj = j; else if (d[j] == 'v') ni = i+1, nj = j; else if (d[j] == '<') ni = i, nj = j-1; else ni = i, nj = j+1; if (0 <= ni && ni < n && 0 <= nj && nj < m) { nex[i*m+j] = ni*m+nj; e[ni*m+nj].push_back(i*m+j); } else nex[i*m+j] = -1; } } for (int i=0; i<n*m; i++) { scanf("%d", &val[i]); assert(-(int)1e9 <= val[i] && val[i] <= (int)1e9); } for (int i=0; i<n*m; i++) f[i][0] = f[i][1] = -B; for (int i=0; i<n*m; i++) if (!vis[i]) fnd_ring(i); for (int i=0; i<n*m; i++) { if (is_r[i]) dp(i); if (nex[i] == -1) { f[i][0] = val[i]; f[i][1] = -val[i]; dp(i); } } ull ans = 0, c = 1; for (int i=0; i<n*m; i++) { ans += (ull)(f[i][0] + B) * c; c *= C; } printf("%llu\n", ans); return 0; }