【AtCoder】NOMURA Programming Competition 2020

比赛链接

点击打开链接

官方题解

点击打开链接

Problem A. Study Scheduling

计算两个时刻的时间间隔,减去 K K K
时间复杂度 O ( 1 ) O(1) O(1)

#include
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int get(int h, int m) {
	return h * 60 + m;
}
int main() {
	int h1, m1, h2, m2, k;
	read(h1), read(m1), read(h2), read(m2), read(k);
	cout << get(h2, m2) - get(h1, m1) - k << endl;
	return 0;
}

Problem B. Postdocs

不难发现对于任意字符串,将某个 P 替换成 D 不会使得答案变小。
因此,将所有问号替换成 D 即可。

时间复杂度 O ( ∣ T ∣ ) O(|T|) O(T)

#include
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
char s[MAXN];
int main() {
	scanf("%s", s + 1);
	int n = strlen(s + 1);
	for (int i = 1; i <= n; i++)
		if (s[i] == '?') s[i] = 'D';
	printf("%s\n", s + 1);
	return 0;
}

Problem C. Folia

对于二叉树上有 x x x 个节点的一层,其上一层可能的非叶节点数为:
⌈ x 2 ⌉ , ⌈ x 2 ⌉ + 1 , ⌈ x 2 ⌉ + 2 , … , x \lceil\frac{x}{2}\rceil,\lceil\frac{x}{2}\rceil+1,\lceil\frac{x}{2}\rceil+2,\dots,x 2x,2x+1,2x+2,,x

由此,我们自底向上可以求出每一层结点总数可能的区间 [ l i , r i ] [l_i,r_i] [li,ri]

问题有解,当且仅当 1 ∈ [ l 0 , r 0 ] 1\in[l_0,r_0] 1[l0,r0]
对于最大化节点总数的问题,我们只需要再自顶向下,进行一遍贪心即可。

时间复杂度 O ( N ) O(N) O(N)

#include
using namespace std;
const int MAXN = 1e5 + 5;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
ll a[MAXN], l[MAXN], r[MAXN];
int main() {
	int n; read(n);
	for (int i = 0; i <= n; i++)
		read(a[i]);
	if (n == 0) {
		if (a[0] == 1) puts("1");
		else puts("-1");
		return 0;
	}
	for (int i = n; i >= 0; i--) {
		l[i] = a[i] + (l[i + 1] + 1) / 2;
		r[i] = a[i] + r[i + 1];
	}
	if (l[0] != 1) {
		puts("-1");
		return 0;
	}
	ll ans = 1, cur = 1;
	for (int i = 1; i <= n; i++) {
		cur -= a[i - 1];
		cur = min(r[i], cur * 2);
		ans += cur;
	}
	cout << ans << endl;
	return 0;
}

Problem D. Urban Planning

每个点出度均不超过 1 1 1 的有向图形成了由一个基环内向树和树组成的森林。
出度为 0 0 0 的点必然是某一棵树的树根。

生成森林的边数 F F F 和图中环的总数 C C C 应当满足:
F = N − C F=N-C F=NC

因此,不妨考虑对 C C C 的总和进行计数。
对于已经形成的环,显然可以简单地进行统计,其贡献即为 ( N − 1 ) k (N-1)^k (N1)k

对于尚未形成的环,其一定是由若干个出度为 0 0 0 的点所在的树连接而成的。
考虑大小为 a 1 , a 2 , … , a n    ( n ≥ 2 ) a_1,a_2,\dots,a_n\;(n\geq2) a1,a2,,an(n2) 的根节点是出度为 0 0 0 的点的树,则在
( n − 1 ) ! × ( N − 1 ) k − n × ∏ i = 1 n a i (n-1)!\times (N-1)^{k-n}\times \prod_{i=1}^{n}a_i (n1)!×(N1)kn×i=1nai
种情况下,这些树恰好连成了环。

特别地,对于 n = 1 n=1 n=1 的情况,情况总数应为
( N − 1 ) k − 1 × ( a 1 − 1 ) (N-1)^{k-1}\times (a_1-1) (N1)k1×(a11)

则设计动态规划加速枚举即可。

时间复杂度 O ( N 2 ) O(N^2) O(N2)

#include
using namespace std;
const int MAXN = 5e3 + 5;
const int P = 1e9 + 7;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int n, p[MAXN], s[MAXN], f[MAXN];
int find(int x) {
	if (f[x] == x) return x;
	else return f[x] = find(f[x]);
}
int fac[MAXN], powk[MAXN], dp[MAXN];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
int main() {
	int k = 0; read(n);
	for (int i = 1; i <= n; i++)
		f[i] = i, s[i] = 1;
	for (int i = 1; i <= n; i++) {
		read(p[i]);
		k += p[i] == -1;
		if (p[i] != -1) {
			int x = find(i), y = find(p[i]);
			if (x != y) {
				f[x] = y;
				s[y] += s[x];
			}
		}
	}
	dp[0] = powk[0] = fac[0] = 1;
	for (int i = 1; i <= n; i++) {
		fac[i] = 1ll * fac[i - 1] * i % P;
		powk[i] = 1ll * powk[i - 1] * (n - 1) % P;
	}
	int ans = 1ll * n * powk[k] % P;
	for (int i = 1; i <= n; i++)
		if (f[i] == i) {
			if (p[i] == -1) {
				for (int j = n; j >= 1; j--)
					update(dp[j], 1ll * dp[j - 1] * s[i] % P);
			} else update(ans, P - powk[k]);
		}
	for (int i = 1; i <= n; i++)
		if (p[i] == -1) update(ans, P - 1ll * (s[i] - 1) * powk[k - 1] % P);
	for (int i = 2; i <= k; i++)
		update(ans, P - 1ll * dp[i] * fac[i - 1] % P * powk[k - i] % P);
	cout << ans << endl;
	return 0;
}

Problem E. Binary Programming

首先,将问题倒过来考虑,由字符串 T T T 开始,我们依次删去一个字符,直至 T T T 为空。

首要的观察是对于存在 0 0 0 的字符串,我们会优先删除 0 0 0 。这是因为对于一个删去 1 1 1 的操作,我们可以选择转而删去相邻的另一个字符,可以发现得到的结果不会变劣。

由此,对于连续的一段 1 1 1 ,我们关心的只是其个数的奇偶性,考虑删去相邻的两个 1 1 1
T T T 将会成为如下样式:
0 … 010 … 010 … 0 … 0\dots010\dots010\dots0\dots 00100100

考虑此时每一个 1 1 1 对答案的贡献,记其之前的 0 0 0 的个数为 p p p ,之后的 0 0 0 的个数为 s s s
则对于奇数位的 1 1 1 ,删去所有 0 0 0 后其对答案的贡献有上界:
⌊ p 2 ⌋ + 1 + s \lfloor\frac{p}{2}\rfloor+1+s 2p+1+s
对于偶数位的 1 1 1 ,删去所有 0 0 0 后其对答案的贡献有上界:
⌊ p + 1 2 ⌋ + s \lfloor\frac{p+1}{2}\rfloor+s 2p+1+s

事实上,这个上界是可以取到的。删去第一段全部的 0 0 0 ,然后从左到右依次删去每一段的若干个零,直至每一段均剩余一个 0 0 0 ,最后从右往左删去所有的 0 0 0 即可取到该上界。

时间复杂度 O ( ∣ T ∣ ) O(|T|) O(T)

#include
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
char s[MAXN]; int t[MAXN], f[MAXN];
int main() {
	scanf("%s", s + 1);
	int n = strlen(s + 1);
	int tot = 0, cnt[2] = {0, 0}; ll ans = 0;
	for (int i = 1; i <= n; i++)
		tot += s[i] == '0';
	for (int i = 1; i <= n; i++)
		if (s[i] == '0') cnt[0]++;
		else {
			if (s[i + 1] == '1') {
				ans += tot + 1, i++;
				cnt[1] += 2;
			} else {
				cnt[1] += 1;
				ans += i % 2;
				ans += tot - cnt[0];
				if (i % 2) ans += cnt[0] / 2;
				else ans += (cnt[0] + 1) / 2;
			}
		}
	for (int i = 1; i <= cnt[1] - 1; i++)
		ans += (i + 1) / 2;
	cout << ans << endl;
	return 0;
}

Problem F. Sorting Game

考虑如何判断某序列是否合法。在最终序列中,不应存在位置对 ( i , j )    ( i ≤ j ) (i,j)\;(i\leq j) (i,j)(ij) ,满足可以通过删去一些二进制位的方式使得 a i > a j a_i>a_j ai>aj ,且 a i a_i ai a j a_j aj 在二进制上相差不止一位。

进一步考虑如何确定两个数 x , y x,y x,y 是否能够通过删去一些二进制位的方式满足条件。

从高到低考虑每一个数位,记当前数位为 c x , c y c_x,c_y cx,cy
( 1 ) (1) (1) 、若 c x = c y c_x=c_y cx=cy ,则显然这是一个不产生影响的数位。
( 2 ) (2) (2) 、若 c y = c x + 1 c_y=c_x+1 cy=cx+1 ,则表明若不删去当前数位,将有 x < y xx<y ,考虑最坏情况的前提下,我们应当删去这个数位,因此这同样是一个不产生影响的数位。
( 3 ) (3) (3) 、最后,若 c x = c y + 1 c_x=c_y+1 cx=cy+1 ,则表明已经有 y < x yy<x ,为了让 ( x , y ) (x,y) (x,y) 满足条件, x , y x,y x,y 在后续的数位上必须完全相等,这同样意味着我们可以将 x , y x,y x,y 看做同一个数。

由此,考虑记 d p i , j dp_{i,j} dpi,j 表示 N = i , M = j N=i,M=j N=i,M=j 时的答案。

若最高位的序列不存在子串 10 10 10 ,即形如
00 … 011 … 1 00\dots011\dots1 000111
则可以直接由 d p i − 1 , j dp_{i-1,j} dpi1,j 转移得到。

否则,考虑最高位第一个出现的 1 1 1 和最后一个出现的 0 0 0 ,例如
00 … 01 x x … x 011 … 1 00\dots01xx\dots x011\dots1 0001xxx0111
1 x x … x 0 1xx\dots x0 1xxx0 中全部的元素的后续数位都应相等,记 x x x 的个数为 c c c ,则可以由 d p i − 1 , j − c − 1 dp_{i-1,j-c-1} dpi1,jc1 转移得到。那么,枚举 x x x 的个数 k k k ,不难得到一个 O ( N × M 2 ) O(N\times M^2) O(N×M2) 的动态规划解法。

#include
using namespace std;
const int MAXN = 5005;
const int P = 1e9 + 7;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
int dp[MAXN][MAXN];
int main() {
	int n, m; read(n), read(m);
	for (int i = 1; i <= m; i++)
		dp[0][i] = 1;
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= m; j++) {
		update(dp[i][j], 1ll * dp[i - 1][j] * (j + 1) % P);
		for (int k = 2; k <= j; k++)
			update(dp[i][j], 1ll * dp[i - 1][j - k + 1] * (j - k + 1) % P * power(2, k - 2) % P);
	}
	cout << dp[n][m] << endl;
	return 0;
}

观察转移,利用部分和简单优化即可。
时间复杂度 O ( N × M ) O(N\times M) O(N×M)

#include
using namespace std;
const int MAXN = 5005;
const int P = 1e9 + 7;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
int dp[MAXN][MAXN];
int main() {
	int n, m; read(n), read(m);
	for (int i = 1; i <= m; i++)
		dp[0][i] = 1;
	for (int i = 1; i <= n; i++) {
		int sum = 0;
		for (int j = 1; j <= m; j++) {
			update(dp[i][j], 1ll * dp[i - 1][j] * (j + 1) % P);
			update(dp[i][j], sum);
			sum = (2ll * sum + 1ll * dp[i - 1][j] * j) % P;
		}
	}
	cout << dp[n][m] << endl;
	return 0;
}

你可能感兴趣的:(【OJ】AtCoder)