Codeforces Round #551 (Div.2) 题解 (翻车记)

mac2022-06-30  148

Codeforces Round #551 (Div.2) 题解

\(Out \ \ of \ \ Competition\) 选手翻车记……

可能这场比赛自己也没怎么认真打,然后……然后就翻车了……

\(C\)题之后的题写写题解吧:

C. Serval and Parenthesis Sequence

一句话题意:给出字符串包含'(', ')', '?',求出一种将'?'替换成'('或者')'的方案,满足这个字符串所有前缀都不是合法括号序列,整个字符串是合法括号序列。

比较显然的贪心吧…… 考虑套路方法,把'('设成\(1\),')'设成\(-1\),那么做前缀和时,如果某一处的\(sum < 0\),说明这个字符串无法成为合法括号序列。否则对于每一个'?',如果可以填'(',那么就填左括号,否则填右括号。如果其中某个非结尾的地方\(sum == 0\),则说明这个前缀形成了合法的括号序,判断无解即可……

Code:

#include <bits/stdc++.h> using namespace std; const int N = 3e5 + 50; int n, c1, c2; char s[N]; int sum = 0; int main() { scanf("%d", &n); if(n & 1) return puts(":("), 0; scanf("%s", s + 1); for(int i = 1; i <= n; i++) { if(s[i] == '(') c1++; else if(s[i] == ')' ) c2++; } for(int i = 1; i <= n; i++) { if(s[i] == '(') { sum ++; } else if(s[i] == ')') { sum --; } else { if(c1 == n / 2) sum--, c2++, s[i] = ')'; else sum++, c1++, s[i] = '('; } if(i != n && sum <= 0) return puts(":("), 0; if(i == n && sum) return puts(":("), 0; } for(int i = 1; i <= n; i++) printf("%c", s[i]); puts(""); return 0; }

D. Serval and Rooted Tree

一句话题意:一个\(n\)个节点的树,每个节点的值是其所有孩子节点的权值的\(\min\)或者\(\max\)。假设有\(k\)个叶子节点,要求给每个叶子节点权值赋成\(1\)\(k\)中的某一个(叶子节点权值不能相同),最大化根节点权值。

又是一道水题……然而刚开始居然没有想法,看来自己真的智商下降了……写\(E\)的时候想出来了,但是\(E\)已经写到一半了,想着\(E\)分高,就打算先把\(E\)写出来,再写\(D\)。然后……就翻车了……

考虑\(dp[x]\)记录\(x\)节点能够取到的权值的排名,\(v\)\(x\)的子节点,如果\(f[x] = 1\),那么\(dp[x] = \min_v dp[v]\),否则\(dp[x] = \sum_v dp[v]\)。这个还是比较显然的,那么答案就是\(n - dp[1] + 1\)

啊啊啊自己真是菜爆了……

Code:

#include <bits/stdc++.h> using namespace std; const int N = 3e5 + 50; const int INF = 1e7; typedef vector<int> Vec; int n, c; int f[N], dp[N]; Vec G[N]; void Dfs(int o) { if(!G[o].size()) return (void) ( dp[o] = 1, c++); for(int i = 0; i < (int) G[o].size(); i++) { int to = G[o][i]; Dfs(to); } if(f[o] == 1) { dp[o] = INF; for(int i = 0; i < (int) G[o].size(); i++) { int to = G[o][i]; dp[o] = min(dp[o], dp[to]); } } else { dp[o] = 0; for(int i = 0; i < (int) G[o].size(); i++) { int to = G[o][i]; dp[o] += dp[to]; } } return ; } int main() { scanf("%d", &n); for(int i = 1; i <= n; i++) scanf("%d", &f[i]); for(int i = 2, x; i <= n; i++) scanf("%d", &x), G[x].push_back(i); Dfs(1); int ans = c - dp[1] + 1; printf("%d\n", ans); return 0; }

E. Serval and Snake

一句话题意:emm……似乎有点难说明啊……将就着看一下题吧……

题解:比较好想的一个二分 + 枚举吧……首先我们从左向右枚举答案端点的列\(i\),查询\((1, 1)\)\((n, i)\)的答案,如果某次询问的答案为奇数,说明一个端点的纵坐标为\(i\),另一个端点也类似。而如果所有的询问都是偶数,那么说明两个端点的纵坐标相同。

先考虑第一种情况,现在我们知道了两个端点的纵坐标\(y\),我们就可以在横坐标上二分,如果查询\((1, y)\)\((mid, y)\)的答案为奇数,说明端点在\([l, mid]\)里,否则在\([mid + 1, r]\)里。这样加上第一次的枚举,总的次数为\(n + log_2 n = 1010\)

然后是第二种情况,我们继续枚举行,查询\((1, 1)\)\((i, n)\)的答案,如果某次的答案为奇数,说明某个端点的横坐标为\(i\),另一个端点也类似。然后列仍然用二分即可。粗略的考虑一下这样的查询的个数最多似乎是\(2 * n + 2 * log_n = 2020\)次,超出了限制……但是实际上第一次枚举最多只会有\(n - 1\),第二次枚举最多只会有\(n - 2\),而两次二分实际上可以只需要一次,所以询问次数不会超过\(2007\)次。而且这个很难卡的……

Code:

#include <bits/stdc++.h> using namespace std; const int N = 1005; struct Point { int x, y; void read() { scanf("%d%d", &x, &y); } }; typedef pair<Point, Point> P; #define fi first #define se second #define mk make_pair int n; int Ask(Point a, Point b) { printf("? %d %d %d %d\n", a.x, a.y, b.x, b.y); fflush(stdout); int x; scanf("%d", &x); if(x == -1) assert(0); return x; } void Answer(Point a, Point b) { printf("! %d %d %d %d\n", a.x, a.y, b.x, b.y); exit(0); } int main() { scanf("%d", &n); Point Ansx, Ansy; int l = 1, r = n; int f = 0; for(int i = 1; i <= n; i++) { Point A, B; A.x = A.y = 1; B.x = n, B.y = i; int c = Ask(A, B); if(c & 1) { f = 1; Ansx.y = i; break; } } if(!f) { for(int i = 1; i <= n; i++) { Point A, B; A.x = A.y = 1; B.x = i, B.y = n; int c = Ask(A, B); if(c & 1) { Ansx.x = i; break; } } int l = 1, r = n; while(l < r) { if(l + 1 == r) { Point A, B; A.x = A.y = 1; B.x = Ansx.x, B.y = l; int c = Ask(A, B); if(c & 1) r--; else l++; break; } int mid = (l + r) >> 1; Point A, B; A.x = A.y = 1; B.x = Ansx.x, B.y = mid; int c = Ask(A, B); if(!(c & 1)) l = mid + 1; else r = mid; } Ansx.y = Ansy.y = l; l = Ansx.x + 1, r = n; while(l < r) { if(l + 1 == r) { Point A, B; A.x = r, A.y = 1; B.x = B.y = n; int c = Ask(A, B); if(c & 1) l++; else r--; break; } int mid = (l + r) >> 1; Point A, B; A.x = mid, A.y = 1; B.x = B.y = n; int c = Ask(A, B); if(!(c & 1)) r = mid - 1; else l = mid; } Ansy.x = l; Answer(Ansx, Ansy); } else { for(int i = n; i >= 1; i--) { Point A, B; A.x = 1, A.y = i; B.x = B.y = n; int c = Ask(A, B); if(c & 1) { Ansy.y = i; break; } } int l = 1, r = n; while(l < r) { if(l + 1 == r) { Point A, B; A.x = 1; A.y = Ansx.y; B.x = l, B.y = Ansx.y; int c = Ask(A, B); if(c & 1) r--; else l++; break; } int mid = (l + r) >> 1; Point A, B; A.x = 1, A.y = Ansx.y; B.x = mid, B.y = Ansx.y; int c = Ask(A, B); if(c & 1) r = mid; else l = mid + 1; } Ansx.x = l; l = 1, r = n; while(l < r) { if(l == r) { Point A, B; A.x = 1, A.y = Ansy.y; B.x = l; B.y = Ansy.y; int c = Ask(A, B); if(c & 1) r--; else l++; break; } int mid = (l + r) >> 1; Point A, B; A.x = 1, A.y = Ansy.y; B.x = mid, B.y = Ansy.y; int c = Ask(A, B); if(c & 1) r = mid; else l = mid + 1; } Ansy.x = l; Answer(Ansx, Ansy); } return 0; }

F. Serval and Bonus Problem

一句话题意:在一个长度为\(l\)的线段上随机\(n\)条线段,求长度为\(l\)的线段中至少被\(k\)个区间覆盖的线段长度之和……

考虑随机\(n\)条线段,就相当于随机\(2n\)个点,而我们就可以把\(l\)均等分\(2n + 1\)段,然后把随机点看成随机这些均等分的点,这样对于题目的答案是不变的。那么我们考虑用\(dp\),计算出有多少条线段被\(k\)个区间覆盖,那么最后单独计算一下贡献即可。

我们记\(dp[i][j]\)表示考虑了前\(i\)个端点,当前没有匹配上的左端点有\(j\)个,那么转移就很显然了,下一个要么是左端点,要么是右端点,注意边界条件判一下……

然后我们开始计算贡献,我们枚举每一个长度为\(\frac{l}{2n + 1}\)区间,那么这个区间被\(k\)个区间覆盖的方案数就是\(f[i][j] * f[n * 2 - i][j] * j!\)。最后由于求的是期望,再除以\(f[n * 2][0]\)即可。

Code:

#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 5005; const int Md = 998244353; inline int Add(const int &x, const int &y) { return (x + y >= Md) ? (x + y - Md) : (x + y); } inline int Sub(const int &x, const int &y) { return (x - y < 0) ? (x - y + Md) : (x - y); } inline int Mul(const int &x, const int &y) { return (ll)x * y % Md; } int Powe(int x, int y = Md - 2) { int ans = 1; for(; y; y >>= 1, x = (ll)x * x % Md) if(y & 1) ans = 1ll * ans * x % Md; return ans; } int n, k, l; int fac[N], Inv[N], f[N][N]; void Init() { fac[0] = Inv[0] = 1; for(int i = 1; i < N; i++) fac[i] = Mul(fac[i - 1], i); Inv[N - 1] = Powe(fac[N - 1]); for(int i = N - 2; i; i--) Inv[i] = Mul(Inv[i + 1], i + 1); } int main() { scanf("%d%d%d", &n, &k, &l); Init(); f[0][0] = 1; for(int i = 0; i <= (n << 1); i++) { for(int j = 0; j <= min(n, i); j++) { f[i + 1][j + 1] = Add(f[i + 1][j + 1], f[i][j]); if(j) f[i + 1][j - 1] = Add(f[i + 1][j - 1], Mul(f[i][j], j)); } } int ans = 0; int len = Mul(l, Powe((n << 1) + 1)); for(int i = 1; i <= (n << 1) - 1; i++) { for(int j = k; j <= min(n, i); j++) ans = Add(ans, Mul(f[i][j], Mul(f[(n << 1) - i][j], fac[j]))); } ans = Mul(ans, Powe(f[n << 1][0])); ans = Mul(ans, len); printf("%d\n", ans); return 0; }

转载于:https://www.cnblogs.com/Apocrypha/p/10707367.html

最新回复(0)