C – Serval and Parenthesis Sequence
这道题主要是运用贪心。首先我们可以确定以下几种情况是肯定无解的:
- 第一个字符是 ‘)’,最后一个字符是 ‘(‘。
- 字符串长度为奇数。
我们发现整个字符串\(s[1 \dots n]\)中,第一个和最后一个字符一定要是 ‘(‘ 和 ‘)’。所以我们只用关心\(s[2 \dots n-1]\)就好。统计需要补充的括号个数:也就是\((n-2)\)减去现已确定的括号个数,分\(remL\)为左括号需要补充的个数、\(remR\)为右括号需要补充的个数。
之后可以考虑贪心替换,优先更换左括号,再更换右括号。最后验一遍即可。
const int MAX_N = 3e5 + 2000;
scanf("%d", &len), scanf("%s", str), n = len;
if ((len & 1) || ((len - 2) & 1) || str[0] == ')' || str[len - 1] == '(')
str[0] = '(', str[len - 1] = ')';
for (int i = 1; i < n - 1; i++)
remL = len / 2 - remL, remR = len / 2 - remR;
for (int i = 1; i < n - 1; i++)
if (str[i] == '?' && remL)
else if (str[i] == '?' && remR)
for (int i = 0; i < n; i++)
if (tot == 0 && i != n - 1)
// C.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 3e5 + 2000;
int len, n;
char str[MAX_N];
void goFuck()
{
printf(":("), exit(0);
}
int main()
{
scanf("%d", &len), scanf("%s", str), n = len;
int remL = 0, remR = 0;
if ((len & 1) || ((len - 2) & 1) || str[0] == ')' || str[len - 1] == '(')
goFuck();
str[0] = '(', str[len - 1] = ')';
len -= 2;
for (int i = 1; i < n - 1; i++)
if (str[i] == '(')
remL++;
else if (str[i] == ')')
remR++;
remL = len / 2 - remL, remR = len / 2 - remR;
for (int i = 1; i < n - 1; i++)
if (str[i] == '?' && remL)
str[i] = '(', remL--;
else if (str[i] == '?' && remR)
str[i] = ')', remR--;
if (remL || remR)
goFuck();
int tot = 0;
for (int i = 0; i < n; i++)
{
if (str[i] == '(')
tot++;
else
tot--;
if (tot == 0 && i != n - 1)
goFuck();
}
if (tot != 0)
goFuck();
cout << str;
return 0;
}
// C.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 3e5 + 2000;
int len, n;
char str[MAX_N];
void goFuck()
{
printf(":("), exit(0);
}
int main()
{
scanf("%d", &len), scanf("%s", str), n = len;
int remL = 0, remR = 0;
if ((len & 1) || ((len - 2) & 1) || str[0] == ')' || str[len - 1] == '(')
goFuck();
str[0] = '(', str[len - 1] = ')';
len -= 2;
for (int i = 1; i < n - 1; i++)
if (str[i] == '(')
remL++;
else if (str[i] == ')')
remR++;
remL = len / 2 - remL, remR = len / 2 - remR;
for (int i = 1; i < n - 1; i++)
if (str[i] == '?' && remL)
str[i] = '(', remL--;
else if (str[i] == '?' && remR)
str[i] = ')', remR--;
if (remL || remR)
goFuck();
int tot = 0;
for (int i = 0; i < n; i++)
{
if (str[i] == '(')
tot++;
else
tot--;
if (tot == 0 && i != n - 1)
goFuck();
}
if (tot != 0)
goFuck();
cout << str;
return 0;
}
D – Serval and Rooted Tree
一道比较有思维难度的好题。
这其实就是一颗决策树,让你填数字进去实现决策最优。我们可以考虑贪心地进行树形 DP,设\(dp[i]\)为答案的贡献,意义为「比答案大的个数」。这样我们可以进行 DP:
\[ dp[u] = \begin{cases} \sum_{i \in son} dp[i], u \in min \\ min_{i \in son} dp[i] \end{cases}, u \in max \]
当然,额外的,叶子节点的值统统为一。最后的答案是 \(k – dp[1] + 1\)。
const int MAX_N = 3e5 + 2000;
int head[MAX_N], current, n, k, fa[MAX_N], deg[MAX_N], dp[MAX_N];
void addpath(int src, int dst)
edges[current].to = dst, edges[current].nxt = head[src];
if (u != 1 && deg[u] == 1)
for (int i = head[u]; i != -1; i = edges[i].nxt)
dp[u] = min(dp[u], dp[edges[i].to]);
dp[u] += dp[edges[i].to];
memset(head, -1, sizeof(head));
for (int i = 1; i <= n; i++)
for (int i = 2; i <= n; i++)
scanf("%d", &fa[i]), addpath(fa[i], i), deg[fa[i]]++, deg[i]++;
printf("%d", k - dp[1] + 1);
// D.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 3e5 + 2000;
int head[MAX_N], current, n, k, fa[MAX_N], deg[MAX_N], dp[MAX_N];
bool mark[MAX_N];
struct edge
{
int to, nxt;
} edges[MAX_N << 1];
void addpath(int src, int dst)
{
edges[current].to = dst, edges[current].nxt = head[src];
head[src] = current++;
}
void dfs(int u)
{
if (u != 1 && deg[u] == 1)
{
k++, dp[u] = 1;
return;
}
if (mark[u])
dp[u] = 0x3f3f3f3f;
else
dp[u] = 0;
for (int i = head[u]; i != -1; i = edges[i].nxt)
{
dfs(edges[i].to);
if (mark[u])
dp[u] = min(dp[u], dp[edges[i].to]);
else
dp[u] += dp[edges[i].to];
}
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &mark[i]);
for (int i = 2; i <= n; i++)
scanf("%d", &fa[i]), addpath(fa[i], i), deg[fa[i]]++, deg[i]++;
dfs(1);
printf("%d", k - dp[1] + 1);
return 0;
}
// D.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 3e5 + 2000;
int head[MAX_N], current, n, k, fa[MAX_N], deg[MAX_N], dp[MAX_N];
bool mark[MAX_N];
struct edge
{
int to, nxt;
} edges[MAX_N << 1];
void addpath(int src, int dst)
{
edges[current].to = dst, edges[current].nxt = head[src];
head[src] = current++;
}
void dfs(int u)
{
if (u != 1 && deg[u] == 1)
{
k++, dp[u] = 1;
return;
}
if (mark[u])
dp[u] = 0x3f3f3f3f;
else
dp[u] = 0;
for (int i = head[u]; i != -1; i = edges[i].nxt)
{
dfs(edges[i].to);
if (mark[u])
dp[u] = min(dp[u], dp[edges[i].to]);
else
dp[u] += dp[edges[i].to];
}
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &mark[i]);
for (int i = 2; i <= n; i++)
scanf("%d", &fa[i]), addpath(fa[i], i), deg[fa[i]]++, deg[i]++;
dfs(1);
printf("%d", k - dp[1] + 1);
return 0;
}
F – Serval and Bonus Problem
这道题好神仙啊。
我们枚举\(2n-1\)个区间,每一段区间的期望长度是\(\frac{l}{2n+1}\)。我们可以在期望意义下认为这些区间长度相等。枚举时,我们可以记 A 为左边点的个数,B 为右边点的个数:

我们考虑这一段区间是没有点的。我们现在要算这种情况的概率。这种情况的条件:
- 左边有 A 个点,右边有 B 个点。
- A 个点中选 k 或更多的点与 B 个点中选 k 或更多的点的配对。
- A 中剩下的点自行配对,B 也是如此。
我们可以开始推式子了。注意到段内配对出现较多,我们定义\(f(x)\)为「\(x\)个点中自行配对的方案数」:
\[ f(x) = \begin{cases} \prod_{i = 1}^\frac{x}{2} (x – (2i-1)), \text{x为偶数} \\ 0, \text{x为奇数} \end{cases} \]
现在推概率,概率就是上面描述的三种情况,除掉\(f(2n)\)即可:
\[ P(A, B, k) = \frac{ {A \choose k } {B \choose k} k! f(A) f(B) }{f(2n)} \]
其中\(k!\)为这些选中的\(2k\)个点的配对个数。现在推期望:
\[ E(A, B, k) = len * \sum_{i = k}^{min(A,B)} P(A,B,i) \\ E(A, B, k) = \frac{l}{2n+1} * \sum_{i = k}^{min(A,B)} \frac{ {A \choose i } {B \choose i} i! f(A) f(B) }{f(2n)} \]
推完了,求和即可。
const int MAX_N = 5100, mod = 998244353;
ll n, k, l, f[MAX_N << 1], level[MAX_N], level_rev[MAX_N];
ll quick_pow(ll bas, ll tim)
return level[x] * level_rev[x - y] % mod * level_rev[y] % mod;
scanf("%d%d%d", &n, &k, &l);
for (int i = 1; i <= n; i++)
f[2 * i] = f[2 * (i - 1)] * (2 * i - 1) % mod;
// Get ready for the levels;
level[1] = level[0] = level_rev[1] = level_rev[0] = 1;
for (int i = 2; i < MAX_N; i++)
level[i] = level[i - 1] * i % mod;
level_rev[MAX_N - 1] = quick_pow(level[MAX_N - 1], mod - 2);
for (int i = MAX_N - 1; i >= 3; i--)
level_rev[i - 1] = level_rev[i] * i % mod;
for (int h = 2; h <= 2 * n; h++)
int A = h - 1, B = 2 * n - h + 1;
for (int i = k; i <= min(A, B); i++)
ll all = comb(A, i) * comb(B, i) % mod * level[i] % mod * f[A - i] % mod * f[B - i] % mod;
answer = (answer + all) % mod;
answer = answer * quick_pow(2 * n + 1, mod - 2) % mod * quick_pow(f[2 * n], mod - 2) % mod * l % mod;
// F.cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX_N = 5100, mod = 998244353;
ll n, k, l, f[MAX_N << 1], level[MAX_N], level_rev[MAX_N];
ll quick_pow(ll bas, ll tim)
{
ll ans = 1;
while (tim)
{
if (tim & 1)
ans = ans * bas % mod;
bas = bas * bas % mod;
tim >>= 1;
}
return ans;
}
ll comb(ll x, ll y)
{
return level[x] * level_rev[x - y] % mod * level_rev[y] % mod;
}
int main()
{
scanf("%d%d%d", &n, &k, &l);
// Get Ready for the f;
f[0] = 1;
for (int i = 1; i <= n; i++)
f[2 * i] = f[2 * (i - 1)] * (2 * i - 1) % mod;
// Get ready for the levels;
level[1] = level[0] = level_rev[1] = level_rev[0] = 1;
for (int i = 2; i < MAX_N; i++)
level[i] = level[i - 1] * i % mod;
level_rev[MAX_N - 1] = quick_pow(level[MAX_N - 1], mod - 2);
for (int i = MAX_N - 1; i >= 3; i--)
level_rev[i - 1] = level_rev[i] * i % mod;
ll answer = 0;
for (int h = 2; h <= 2 * n; h++)
{
int A = h - 1, B = 2 * n - h + 1;
for (int i = k; i <= min(A, B); i++)
{
ll all = comb(A, i) * comb(B, i) % mod * level[i] % mod * f[A - i] % mod * f[B - i] % mod;
answer = (answer + all) % mod;
}
}
answer = answer * quick_pow(2 * n + 1, mod - 2) % mod * quick_pow(f[2 * n], mod - 2) % mod * l % mod;
printf("%lld", answer);
return 0;
}
// F.cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX_N = 5100, mod = 998244353;
ll n, k, l, f[MAX_N << 1], level[MAX_N], level_rev[MAX_N];
ll quick_pow(ll bas, ll tim)
{
ll ans = 1;
while (tim)
{
if (tim & 1)
ans = ans * bas % mod;
bas = bas * bas % mod;
tim >>= 1;
}
return ans;
}
ll comb(ll x, ll y)
{
return level[x] * level_rev[x - y] % mod * level_rev[y] % mod;
}
int main()
{
scanf("%d%d%d", &n, &k, &l);
// Get Ready for the f;
f[0] = 1;
for (int i = 1; i <= n; i++)
f[2 * i] = f[2 * (i - 1)] * (2 * i - 1) % mod;
// Get ready for the levels;
level[1] = level[0] = level_rev[1] = level_rev[0] = 1;
for (int i = 2; i < MAX_N; i++)
level[i] = level[i - 1] * i % mod;
level_rev[MAX_N - 1] = quick_pow(level[MAX_N - 1], mod - 2);
for (int i = MAX_N - 1; i >= 3; i--)
level_rev[i - 1] = level_rev[i] * i % mod;
ll answer = 0;
for (int h = 2; h <= 2 * n; h++)
{
int A = h - 1, B = 2 * n - h + 1;
for (int i = k; i <= min(A, B); i++)
{
ll all = comb(A, i) * comb(B, i) % mod * level[i] % mod * f[A - i] % mod * f[B - i] % mod;
answer = (answer + all) % mod;
}
}
answer = answer * quick_pow(2 * n + 1, mod - 2) % mod * quick_pow(f[2 * n], mod - 2) % mod * l % mod;
printf("%lld", answer);
return 0;
}