AtCoder NOMURA 2020 题解

题目传送门:AtCoder NOMURA Programming Competition 2020

A - Study Scheduling

略。

#include <cstdio>

int main() {
	int H1, M1, H2, M2, K;
	scanf("%d%d%d%d%d", &H1, &M1, &H2, &M2, &K);
	printf("%d
", (H2 - H1) * 60 + M2 - M1 - K);
	return 0;
}

B - Postdocs

易证存在一种最优解为把所有 ? 替换成 D

#include <cstdio>

char str[200005];

int main() {
	scanf("%s", str + 1);
	for (int i = 1; str[i]; ++i)
		printf("%c", str[i] == '?' ? 'D' : str[i]);
	puts("");
	return 0;
}

C - Folia

(C_d) 为最优方案中深度为 (d) 的非叶子节点数量。

则有限制 (C_d le 2 C_{d - 1} - A_d)(C_d le C_{d + 1} + A_{d + 1})

(d = 0)(d = N) 向中心推进限制即可。

#include <cstdio>

typedef long long LL;
const int MN = 200005;

int N;
LL A[MN], B[MN], C[MN];

int main() {
	scanf("%d", &N);
	for (int i = 0; i <= N; ++i) scanf("%lld", &A[i]);
	if (!N) return puts(A[0] == 1 ? "1" : "-1"), 0;
	for (int i = N; i >= 0; --i) B[i] = B[i + 1] + A[i];
	C[0] = 1 - A[0];
	for (int i = 1; i <= N; ++i) {
		C[i] = 2 * C[i - 1] - A[i];
		if (C[i] < 0 || C[i] + A[i] == 0) return puts("-1"), 0;
		if (C[i] + A[i] > B[i]) C[i] = B[i] - A[i];
	}
	LL Ans = 0;
	for (int i = 0; i <= N; ++i) Ans += A[i] + C[i];
	printf("%lld
", Ans);
	return 0;
}

D - Urban Planning

假设原图由 (M) 个基环内向树和 (K) 个内向树构成。
并且令 (K) 个内向树的大小依次为 (b_1, b_2, ldots , b_K)

要求将给定点对连通的最小边数,也就相当于求 (N) 减去连通块个数。
只要求出所有图中的连通块个数,再用 (N {(N - 1)}^K) 减去这个值即可。

显然至少有 (M) 个连通块,给总连通块个数贡献 (M {(N - 1)}^K)

在其余的 (K) 个内向树中,可以将每个内向树看作一个整体,每在其中形成一个环,就为连通块个数贡献 (1)

考虑枚举某个环中的所有内向树,假设为 (k)(k ge 1))个,为 ({ a_1, a_2, ldots , a_k })(无顺序)。
那么它们形成环的方案数就为 (displaystyle (k - 1)! prod_{i = 1}^{k} b_{a_i})

即枚举一种圆排列(共有 ((k - 1)!) 种),每一个内向树的根可以连向下一个内向树的任何一个点,所以把所有 (b) 值相乘即可。

除了一种例外情况,当 (k = 1) 时,方案数应该为 ((b_{a_1} - 1)),因为有不能连向自己的限制。

据此可以考虑一个 DP:对于每个 (k) 满足 (1 le k le K),求出在所有内向树中选出 (k) 个时它们的 (b) 值的乘积之和。

依次加入每个内向树即可维护。

求出后每项乘上 ((k - 1)!),再乘上 ({(N - 1)}^{K - k})(剩下的其它内向树的方案数)贡献给答案即可,对于 (k = 1) 特殊处理一下。

#include <cstdio>
#include <vector>

typedef long long LL;
const int Mod = 1000000007;
const int MN = 5005;

inline int qPow(int b, int e) {
	int a = 1;
	for (; e; e >>= 1, b = (LL)b * b % Mod)
		if (e & 1) a = (LL)a * b % Mod;
	return a;
}

int N, M, A[MN], Fac[MN];
std::vector<int> G[MN];
int K, B[MN];

int vis[MN];
void DFS(int u) {
	++B[K], vis[u] = 1;
	for (int v : G[u]) DFS(v);
}
void DFS2(int u, int o) {
	vis[u] = 1;
	for (int v : G[u])
		if (!vis[v]) DFS2(v, o);
		else if (v == o) ++M;
}

int dp[MN][MN];

int main() {
	scanf("%d", &N), Fac[0] = 1;
	for (int i = 1; i <= N; ++i) scanf("%d", &A[i]), Fac[i] = (LL)Fac[i - 1] * i % Mod;
	for (int i = 1; i <= N; ++i) if (~A[i]) G[A[i]].push_back(i);
	for (int i = 1; i <= N; ++i) if (!~A[i]) ++K, DFS(i);
	for (int i = 1; i <= N; ++i) if (!vis[i]) DFS2(i, i);
	dp[0][0] = 1;
	for (int i = 1; i <= K; ++i) {
		dp[i][0] = 1;
		for (int j = 1; j <= i; ++j)
			dp[i][j] = (dp[i - 1][j] + (LL)dp[i - 1][j - 1] * B[i]) % Mod;
	}
	dp[K][1] -= K;
	int Ans = (LL)M * qPow(N - 1, K) % Mod;
	for (int j = 1; j <= K; ++j) Ans = (Ans + (LL)dp[K][j] * Fac[j - 1] % Mod * qPow(N - 1, K - j)) % Mod;
	Ans = ((LL)N * qPow(N - 1, K) % Mod - Ans + Mod) % Mod;
	printf("%d
", Ans);
	return 0;
}

E - Binary Programming

(N = |T|),等价于执行以下操作 (N) 次:

  • 所有奇数位的数之和贡献给答案。
  • 删去任意一位。

首先可以发现当 0 还没删光时,删 1 是不优的。

然而当 0 删光了的时候,之后的操作对答案的贡献就很显然了。所以这里考虑如何删 0 是最优的。

可以发现如果有两个相邻的 1,那么每次操作时它们对答案的贡献都是 (1)。所以可以先不考虑。

那么一直删除相邻的两个 1 后,只剩下 (k)1 时,就形成了 (a_0mathtt{0} quad mathtt{1} quad a_1mathtt{0} quad mathtt{1} quad cdots quad mathtt{1} quad a_kmathtt{0}) 的情况。

对于其中的第 (i)1,它最多给答案贡献 ((a_0 + cdots + a_{i - 1})) 的一半(取整方式取决于位置的奇偶性)加 ((a_i + cdots + a_k))

而事实上这个上界是可以被达到的,也就是需要对每个 1 都满足在删它后面的 0 时它的位置在奇数:
先把 (a_0) 个最开始的 0 删光,然后删 (a_1 - 1)0 留下恰好一个,以此类推,删掉 (a_{k - 1} - 1)0 留下恰好一个。
这时形如 (mathtt{1010100000000 cdots}),然后从右到左把所有的 0 删掉即可。

所以使用上述结论求答案即可。

#include <cstdio>
#include <cstring>

typedef long long LL;
const int MN = 200005;

int N, C0, C1, K;
char T[MN];
int stk[MN], cnt[MN], tp;
LL Ans;

int main() {
	scanf("%s", T + 1), N = strlen(T + 1);
	for (int i = 1; i <= N; ++i) ++(T[i] & 1 ? C1 : C0);
	Ans = (LL)(C1 + 1) * (C1 + 1) / 4;
	T[0] = T[N + 1] = '0';
	for (int i = 0; i <= N + 1; ++i) {
		int x = T[i] - '0';
		if (!x) {
			if (tp && !stk[tp]) ++cnt[tp];
			else stk[++tp] = 0, cnt[tp] = 1;
		} else {
			if (tp && stk[tp]) --tp, Ans += C0;
			else stk[++tp] = 1, cnt[tp] = 1;
		}
	} --cnt[1], --cnt[tp];
	for (int i = 1, s = 0; 2 * i <= tp; ++i)
		s += cnt[2 * i - 1],
		Ans += (s + i + 1) / 2 - (i + 1) / 2 + (C0 - s);
	printf("%lld
", Ans);
	return 0;
}

F - Sorting Game

注意到,一个序列是合法的,当且仅当对于所有的两个下标 (i, j)(1 le i < j le M)),都有:
按二进制位从高到低比较 (a_i)(a_j),出现 (a_i) 该位为 (1)(a_j) 该位为 (0) 后,其余的更低位 (a_i) 必须与 (a_j) 完全相同。

考虑 (a_1 sim a_M) 的最高位,有两种情况:

  1. 依次为形如 (mathtt{000111}),也就是先 (0)(1):不存在两个下标先 (1)(0),转化为 ((N - 1, M)) 的子问题,共有 ((M + 1)) 种方案。
  2. 依次为形如 (mathtt{0001????0111}),也就是删去前缀连续的 (0) 和后缀连续的 (1) 后非空的情况:
    所以删去后的那一部分的更低位必须都全部相同了,而前缀连续的 (0) 和后缀连续的 (1) 则不受任何影响。
    这就相当于把中间的部分缩成同一个数了,假设缩完之后的长度为 (i),则转化为 ((N - 1, i)) 的子问题。
    假设缩完之后的长度为 (i),实际上有恰好 (i) 种方案去分配被缩起来的部分,而对于 (mathtt{????}) 部分,共有 (2^{M - i - 1}) 种方案。

也就是说,有如下 DP 转移方程:

[egin{aligned} mathrm{f}[n][m] &= (m + 1) cdot mathrm{f}[n - 1][m] + sum_{i = 1}^{m - 1} i cdot 2^{m - i - 1} cdot mathrm{f}[n - 1][i] end{aligned} ]

后面的部分可以使用前缀和的思想以加速处理,时间复杂度为 (mathcal O (N M))

#include <cstdio>
#include <vector>

typedef long long LL;
const int Mod = 1000000007;
const int MN = 5005;

int N, M, f[MN][MN];

int main() {
	scanf("%d%d", &N, &M);
	for (int j = 1; j <= M; ++j) f[0][j] = 1;
	for (int i = 1; i <= N; ++i) {
		for (int j = 2; j <= M; ++j)
			f[i][j] = (2 * f[i][j - 1] + (LL)f[i - 1][j - 1] * (j - 1)) % Mod;
		for (int j = 1; j <= M; ++j)
			f[i][j] = (f[i][j] + (LL)f[i - 1][j] * (j + 1)) % Mod;
	}
	printf("%d
", f[N][M]);
	return 0;
}
原文地址:https://www.cnblogs.com/PinkRabbit/p/AtCoderNOMURA2020.html