Codeforces Round #612 (Div. 2)
C. Garland
直接无脑dp。
dp[i][j][0/1] 表示前 $i$ 个数里还剩 $j$ 个奇数可以用,最后一位的奇偶性的最小值。
然后由上一位转移就行了。
#include <bits/stdc++.h> const int N = 110, INF = 0x3f3f3f3f; int dp[N][N / 2][2], p[N]; inline bool chkmin(int &a, int b) { return a > b ? a = b, 1 : 0; } inline bool chkmax(int &a, int b) { return a < b ? a = b, 1 : 0; } int main() { int n; scanf("%d", &n); const int cnt = (n + 1) / 2; for (int i = 1; i <= n; i++) scanf("%d", p + i); memset(dp, 0x3f, sizeof(dp)); if (p[1]) { int id = p[1] & 1; if (id) dp[1][cnt - 1][id] = 0; else dp[1][cnt][id] = 0; } else { dp[1][cnt - 1][1] = dp[1][cnt][0] = 0; } for (int i = 2; i <= n; i++) { for (int j = 0; j <= cnt; j++) { if (!p[i]) { chkmin(dp[i][j][1], std::min(dp[i - 1][j + 1][1], dp[i - 1][j + 1][0] + 1)); chkmin(dp[i][j][0], std::min(dp[i - 1][j][1] + 1, dp[i - 1][j][0])); } else { if (p[i] & 1) chkmin(dp[i][j][1], std::min(dp[i - 1][j + 1][1], dp[i - 1][j + 1][0] + 1)); else chkmin(dp[i][j][0], std::min(dp[i - 1][j][1] + 1, dp[i - 1][j][0])); } } } printf("%d\n", std::min(dp[n][0][0], dp[n][0][1])); return 0; }
D. Numbers on Tree
方案不存在当且仅当 $c_u \geq size_u$
dfs 这棵树,将子树的值插入到该节点上,两棵子树之间的值互不影响,那么可以排序后按大小关系重新赋值,再把该节点的值插入,其之后的值依次加 $1$ 即可。
#include <bits/stdc++.h> #define pii pair<int, int> #define fi first #define se second const int N = 2e3 + 7; std::vector<int> adj[N]; std::vector<std::pii> vec[N]; int n, c[N], root; bool flag = 1; void dfs(int u) { for (int i = 0, sz = adj[u].size(); i < sz; i++) { int v = adj[u][i]; dfs(v); if (!flag) return; for (int j = 0; j < vec[v].size(); j++) vec[u].push_back(vec[v][j]); vec[v].clear(); } if (c[u] > vec[u].size()) { flag = 0; return; } std::sort(vec[u].begin(), vec[u].end()); for (int i = 0; i < vec[u].size(); i++) vec[u][i].fi = i + 1; vec[u].insert(vec[u].begin() + c[u], std::pii(c[u] + 1, u)); for (int i = c[u] + 1; i < vec[u].size(); i++) vec[u][i].fi++; } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) { int p; scanf("%d%d", &p, c + i); if (!p) root = i; else adj[p].push_back(i); } dfs(root); if (!flag) { puts("NO"); } else { for (int i = 0; i < n; i++) std::swap(vec[root][i].fi, vec[root][i].se); std::sort(vec[root].begin(), vec[root].end()); puts("YES"); for (int i = 0; i < n; i++) printf("%d%c", vec[root][i].se, " \n"[i == n - 1]); } return 0; }
E1. Madhouse (Easy version)
询问一下 $[1...n]$,再询问一下 $[1...n-1]$,把所有后缀排个序,那么长度为 $1$ 的子串就相差第 $n$ 个字符,长度为 $2$ 的子串就相差第 $n-1$ 个字符和第 $n$ 个字符,这么依次做下去就行了
#include <bits/stdc++.h> void out(int l, int r) { std::cout << "? " << l << " " << r << std::endl; //std::cout.flush(); } int cnt0[105], cnt1[105], dif[105]; char ans[105]; int main() { int n; std::cin >> n; if (n <= 3) { for (int i = 0; i < n; i++) { out(i + 1, i + 1); std::string temp; std::cin >> temp; ans[i] = temp[0]; } std::cout << "! " << ans << std::endl; return 0; } out(1, n); for (int i = 0; i < n * (n + 1) / 2; i++) { std::string str; std::cin >> str; int len = str.length(); for (int j = 0; j < len; j++) cnt0[len] += str[j] - 'a'; } out(1, n - 1); for (int i = 0; i < n * (n - 1) / 2; i++) { std::string str; std::cin >> str; int len = str.length(); for (int j = 0; j < len; j++) cnt1[len] += str[j] - 'a'; } for (int i = 1; i <= n; i++) { dif[i] = cnt0[i] - cnt1[i]; ans[n - i] = dif[i] - dif[i - 1] + 'a'; } std::cout << "! " << ans << std::endl; return 0; }
E2. Madhouse (Hard version)
先用上一个问题的解法询问出前 $\frac{n}{2}$ 个字符,再询问一次 $[1...n]$ 的所有子串。
考虑一个字符串 $abcde$,长度为 $1$ 的子串为 $a$, $b$, $c$, $d$, $e$,长度为 $2$ 的子串为 $ab$, $bc$, $cd$, $de$,出现次数一样的字符是 $a$, $e$,即首尾两个字符,同时我们知道了第一个字符,就能知道最后一个字符是什么,长度为 $3$ 的子串为 $abc$, $bcd$, $cde$,和长度为 $2$ 的子串出现次数一样的字符为 $a$, $e$, $b$, $c$ 同样我们能够得到倒数第二个字符,这样一直做下去即可。
#include <bits/stdc++.h> void out(int l, int r) { std::cout << "? " << l << " " << r << std::endl; //std::cout.flush(); } int cnt0[105], cnt1[105], dif[105], cnt[105][27]; char ans[105]; void solve(int n) { out(1, n); for (int i = 0; i < n * (n + 1) / 2; i++) { std::string str; std::cin >> str; int len = str.length(); for (int j = 0; j < len; j++) cnt0[len] += str[j] - 'a'; } out(1, n - 1); for (int i = 0; i < n * (n - 1) / 2; i++) { std::string str; std::cin >> str; int len = str.length(); for (int j = 0; j < len; j++) cnt1[len] += str[j] - 'a'; } for (int i = 1; i <= n; i++) { dif[i] = cnt0[i] - cnt1[i]; ans[n - i] = dif[i] - dif[i - 1] + 'a'; } } int main() { int n; std::cin >> n; if (n <= 3) { for (int i = 0; i < n; i++) { out(i + 1, i + 1); std::string temp; std::cin >> temp; ans[i] = temp[0]; } std::cout << "! " << ans << std::endl; return 0; } solve((n + 1) / 2); out(1, n); for (int i = 1; i <= (n + 1) * n / 2; i++) { std::string s; std::cin >> s; int len = s.length(); for (int j = 0; j < len; j++) cnt[len][s[j] - 'a']++; } for (int j = (n + 1) / 2 + 1; j <= n; j++) { int i = j - (n + 1) / 2; for (int k = 0; k < 26; k++) { int c = cnt[1][k] - (cnt[i + 1][k] - cnt[i][k]); if (c) { for (int z = 0; z < i; z++) { if (ans[z] == k + 'a') c--; } for (int z = n - 1; z > n - i; z--) { if (ans[z] == k + 'a') c--; } if (c > 0) { ans[n - i] = 'a' + k; break; } } } } std::cout << "! " << ans << std::endl; return 0; }
F. LCC
碰撞出现在相邻两个粒子运动,一:都向左,二:都向右,三:向中间,我们可以分别计算出这三种情况是否会碰撞以及碰撞时间。根据期望的线性性,答案就等于碰撞的时间乘以它是第一次碰撞的概率的总和。我们将所有碰撞的情况排个序,第 $i$ 次碰撞作为第一个发生的概率为前 $i-1$ 次碰撞都不发生的概率减去前 $i$ 次碰撞都不发生的概率。这个某个事件是否发生的概率可以用线段树维护。具体用 tree[p][0/1][0/1] 表示该区间最左边的端点的朝向和该区间最右边的端点的朝向的概率,用一个 limit[i][0/1][0/1] 表示 $i$ 粒子和 $i+1$ 粒子是否能向对应朝向运动,每次修改就在limit上打上标记。线段树的pushup 就用中点来转移即可。
#include <bits/stdc++.h> #define db long double const int N = 3e5 + 7, MOD = 998244353; const db eps = 1e-15; void M(int &a) { if (a >= MOD) a -= MOD; if (a < 0) a += MOD; } int sign(db x) { if (x < -eps) return -1; return x > eps; } int qp(int a, int b = MOD - 2) { int ans = 1; while (b) { if (b & 1) ans = 1LL * ans * a % MOD; a = 1LL * a * a % MOD; b >>= 1; } return ans % MOD; } const int inv = qp(100); int n, pro[N][2]; bool limit[N][2][2]; namespace seg { #define lp p << 1 #define rp p << 1 | 1 static const int XN = N * 4; int tree[XN][2][2]; void pushup(int p, int mid) { for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) { tree[p][i][j] = 0; for (int k = 0; k < 2; k++) for (int l = 0; l < 2; l++) if (!limit[mid][k][l]) M(tree[p][i][j] += 1LL * tree[lp][i][k] * tree[rp][l][j] % MOD); } } void build(int p, int l, int r) { if (l == r) { tree[p][1][1] = pro[l][1]; tree[p][0][0] = pro[l][0]; return; } int mid = l + r >> 1; build(lp, l, mid); build(rp, mid + 1, r); pushup(p, mid); } void update(int p, int l, int r, int pos) { if (l == r) return; int mid = l + r >> 1; if (pos <= mid) update(lp, l, mid, pos); else update(rp, mid + 1, r, pos); pushup(p, mid); } int query() { int ans = 0; for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) M(ans += tree[1][i][j]); return ans; } } struct Node { int x, xp, yp; db t; int tt; Node() {} Node(int x, int xp, int yp, db t, int tt): x(x), xp(xp), yp(yp), t(t), tt(tt) {} bool operator < (const Node &p) const { return sign(t - p.t) < 0; } } P[N]; int main() { scanf("%d", &n); int cnt = 0; int lastx = 0, lastv = 0; for (int i = 1; i <= n; i++) { int x, v, p; scanf("%d%d%d", &x, &v, &p); if (i > 1) { int dis = x - lastx; ++cnt; P[cnt] = Node(i - 1, 1, 0, dis / 1.0 / (lastv + v), 1LL * dis * qp(lastv + v) % MOD); if (v > lastv) { ++cnt; P[cnt] = Node(i - 1, 0, 0, dis / 1.0 / (v - lastv), 1LL * dis * qp(v - lastv) % MOD); } else if (lastv > v) { ++cnt; P[cnt] = Node(i - 1, 1, 1, dis / 1.0 / (lastv - v), 1LL * dis * qp(lastv - v) % MOD); } } lastv = v; lastx = x; pro[i][0] = 1LL * (100 - p) * inv % MOD; pro[i][1] = 1LL * p * inv % MOD; } seg::build(1, 1, n); std::sort(P + 1, P + 1 + cnt); int ans = 0, lastp = 1; for (int i = 1; i <= cnt; i++) { Node p = P[i]; limit[p.x][p.xp][p.yp] = 1; seg::update(1, 1, n, p.x); int curp = seg::query(); M(ans += 1LL * p.tt * (lastp - curp + MOD) % MOD); lastp = curp; } printf("%d\n", ans); return 0; }