比賽鏈接
官方題解
Problem A. Study Scheduling
計算兩個時刻的時間間隔,減去 。
時間複雜度 。
#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 即可。
時間複雜度 。
#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
對於二叉樹上有 個節點的一層,其上一層可能的非葉節點數爲:
由此,我們自底向上可以求出每一層結點總數可能的區間 。
問題有解,當且僅當 。
對於最大化節點總數的問題,我們只需要再自頂向下,進行一遍貪心即可。
時間複雜度 。
#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
每個點出度均不超過 的有向圖形成了由一個基環內向樹和樹組成的森林。
出度爲 的點必然是某一棵樹的樹根。
生成森林的邊數 和圖中環的總數 應當滿足:
因此,不妨考慮對 的總和進行計數。
對於已經形成的環,顯然可以簡單地進行統計,其貢獻即爲 。
對於尚未形成的環,其一定是由若干個出度爲 的點所在的樹連接而成的。
考慮大小爲 的根節點是出度爲 的點的樹,則在
種情況下,這些樹恰好連成了環。
特別地,對於 的情況,情況總數應爲
則設計動態規劃加速枚舉即可。
時間複雜度 。
#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
首先,將問題倒過來考慮,由字符串 開始,我們依次刪去一個字符,直至 爲空。
首要的觀察是對於存在 的字符串,我們會優先刪除 。這是因爲對於一個刪去 的操作,我們可以選擇轉而刪去相鄰的另一個字符,可以發現得到的結果不會變劣。
由此,對於連續的一段 ,我們關心的只是其個數的奇偶性,考慮刪去相鄰的兩個 。
則 將會成爲如下樣式:
考慮此時每一個 對答案的貢獻,記其之前的 的個數爲 ,之後的 的個數爲 。
則對於奇數位的 ,刪去所有 後其對答案的貢獻有上界:
對於偶數位的 ,刪去所有 後其對答案的貢獻有上界:
事實上,這個上界是可以取到的。刪去第一段全部的 ,然後從左到右依次刪去每一段的若干個零,直至每一段均剩餘一個 ,最後從右往左刪去所有的 即可取到該上界。
時間複雜度 。
#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
考慮如何判斷某序列是否合法。在最終序列中,不應存在位置對 ,滿足可以通過刪去一些二進制位的方式使得 ,且 和 在二進制上相差不止一位。
進一步考慮如何確定兩個數 是否能夠通過刪去一些二進制位的方式滿足條件。
從高到低考慮每一個數位,記當前數位爲 。
、若 ,則顯然這是一個不產生影響的數位。
、若 ,則表明若不刪去當前數位,將有 ,考慮最壞情況的前提下,我們應當刪去這個數位,因此這同樣是一個不產生影響的數位。
、最後,若 ,則表明已經有 ,爲了讓 滿足條件, 在後續的數位上必須完全相等,這同樣意味着我們可以將 看做同一個數。
由此,考慮記 表示 時的答案。
若最高位的序列不存在子串 ,即形如
則可以直接由 轉移得到。
否則,考慮最高位第一個出現的 和最後一個出現的 ,例如
則 中全部的元素的後續數位都應相等,記 的個數爲 ,則可以由 轉移得到。那麼,枚舉 的個數 ,不難得到一個 的動態規劃解法。
#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;
}
觀察轉移,利用部分和簡單優化即可。
時間複雜度 。
#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;
}