zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    JVM基础(一)—— 运行时内存结构
    SQL if 和 case when查询示例
    SQL分组聚合查询
    My music
    DataX增量同步到ADB ADS时报错
    shell find的用法
    pycharm安装
    在两个库中查找出差异明细
    docker下安装centos
    升级RDS8.0遇到的问题
  • 原文地址:https://www.cnblogs.com/PinkRabbit/p/AtCoderNOMURA2020.html
Copyright © 2011-2022 走看看