Codeforces Round #551 (Div.2) 题解
\(Out \ \ of \ \ Competition\) 选手翻车记……
可能这场比赛自己也没怎么认真打,然后……然后就翻车了……
\(C\)题之后的题写写题解吧:
C. Serval and Parenthesis Sequence
一句话题意:给出字符串包含'(', ')', '?',求出一种将'?'替换成'('或者')'的方案,满足这个字符串所有前缀都不是合法括号序列,整个字符串是合法括号序列。
比较显然的贪心吧……
考虑套路方法,把'('设成\(1\),')'设成\(-1\),那么做前缀和时,如果某一处的\(sum < 0\),说明这个字符串无法成为合法括号序列。否则对于每一个'?',如果可以填'(',那么就填左括号,否则填右括号。如果其中某个非结尾的地方\(sum == 0\),则说明这个前缀形成了合法的括号序,判断无解即可……
Code:
#include
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
using namespace std;
const int N = 3e5 + 50;
const int INF = 1e7;
typedef vector 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
using namespace std;
const int N = 1005;
struct Point {
int x, y;
void read() {
scanf("%d%d", &x, &y);
}
};
typedef pair 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
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;
}