【AtCoder】NOMURA Programming Competition 2020

比賽鏈接

點擊打開鏈接

官方題解

點擊打開鏈接

Problem A. Study Scheduling

計算兩個時刻的時間間隔,減去 KK
時間複雜度 O(1)O(1)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> 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|)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> 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

對於二叉樹上有 xx 個節點的一層,其上一層可能的非葉節點數爲:
x2,x2+1,x2+2,,x\lceil\frac{x}{2}\rceil,\lceil\frac{x}{2}\rceil+1,\lceil\frac{x}{2}\rceil+2,\dots,x

由此,我們自底向上可以求出每一層結點總數可能的區間 [li,ri][l_i,r_i]

問題有解,當且僅當 1[l0,r0]1\in[l_0,r_0]
對於最大化節點總數的問題,我們只需要再自頂向下,進行一遍貪心即可。

時間複雜度 O(N)O(N)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> 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

每個點出度均不超過 11 的有向圖形成了由一個基環內向樹和樹組成的森林。
出度爲 00 的點必然是某一棵樹的樹根。

生成森林的邊數 FF 和圖中環的總數 CC 應當滿足:
F=NCF=N-C

因此,不妨考慮對 CC 的總和進行計數。
對於已經形成的環,顯然可以簡單地進行統計,其貢獻即爲 (N1)k(N-1)^k

對於尚未形成的環,其一定是由若干個出度爲 00 的點所在的樹連接而成的。
考慮大小爲 a1,a2,,an  (n2)a_1,a_2,\dots,a_n\;(n\geq2) 的根節點是出度爲 00 的點的樹,則在
(n1)!×(N1)kn×i=1nai(n-1)!\times (N-1)^{k-n}\times \prod_{i=1}^{n}a_i
種情況下,這些樹恰好連成了環。

特別地,對於 n=1n=1 的情況,情況總數應爲
(N1)k1×(a11)(N-1)^{k-1}\times (a_1-1)

則設計動態規劃加速枚舉即可。

時間複雜度 O(N2)O(N^2)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e3 + 5;
const int P = 1e9 + 7;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> 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

首先,將問題倒過來考慮,由字符串 TT 開始,我們依次刪去一個字符,直至 TT 爲空。

首要的觀察是對於存在 00 的字符串,我們會優先刪除 00 。這是因爲對於一個刪去 11 的操作,我們可以選擇轉而刪去相鄰的另一個字符,可以發現得到的結果不會變劣。

由此,對於連續的一段 11 ,我們關心的只是其個數的奇偶性,考慮刪去相鄰的兩個 11
TT 將會成爲如下樣式:
001001000\dots010\dots010\dots0\dots

考慮此時每一個 11 對答案的貢獻,記其之前的 00 的個數爲 pp ,之後的 00 的個數爲 ss
則對於奇數位的 11 ,刪去所有 00 後其對答案的貢獻有上界:
p2+1+s\lfloor\frac{p}{2}\rfloor+1+s
對於偶數位的 11 ,刪去所有 00 後其對答案的貢獻有上界:
p+12+s\lfloor\frac{p+1}{2}\rfloor+s

事實上,這個上界是可以取到的。刪去第一段全部的 00 ,然後從左到右依次刪去每一段的若干個零,直至每一段均剩餘一個 00 ,最後從右往左刪去所有的 00 即可取到該上界。

時間複雜度 O(T)O(|T|)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> 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)  (ij)(i,j)\;(i\leq j) ,滿足可以通過刪去一些二進制位的方式使得 ai>aja_i>a_j ,且 aia_iaja_j 在二進制上相差不止一位。

進一步考慮如何確定兩個數 x,yx,y 是否能夠通過刪去一些二進制位的方式滿足條件。

從高到低考慮每一個數位,記當前數位爲 cx,cyc_x,c_y
(1)(1) 、若 cx=cyc_x=c_y ,則顯然這是一個不產生影響的數位。
(2)(2) 、若 cy=cx+1c_y=c_x+1 ,則表明若不刪去當前數位,將有 x<yx<y ,考慮最壞情況的前提下,我們應當刪去這個數位,因此這同樣是一個不產生影響的數位。
(3)(3) 、最後,若 cx=cy+1c_x=c_y+1 ,則表明已經有 y<xy<x ,爲了讓 (x,y)(x,y) 滿足條件, x,yx,y 在後續的數位上必須完全相等,這同樣意味着我們可以將 x,yx,y 看做同一個數。

由此,考慮記 dpi,jdp_{i,j} 表示 N=i,M=jN=i,M=j 時的答案。

若最高位的序列不存在子串 1010 ,即形如
00011100\dots011\dots1
則可以直接由 dpi1,jdp_{i-1,j} 轉移得到。

否則,考慮最高位第一個出現的 11 和最後一個出現的 00 ,例如
0001xxx011100\dots01xx\dots x011\dots1
1xxx01xx\dots x0 中全部的元素的後續數位都應相等,記 xx 的個數爲 cc ,則可以由 dpi1,jc1dp_{i-1,j-c-1} 轉移得到。那麼,枚舉 xx 的個數 kk ,不難得到一個 O(N×M2)O(N\times M^2) 的動態規劃解法。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
const int P = 1e9 + 7;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> 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)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
const int P = 1e9 + 7;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章