比賽鏈接
官方題解
Problem A. Takahashikun, The Strider
可以發現,任意時刻,玩家均位於以前兩次操作路徑的中垂線的交點上。
因此,答案即爲使得玩家朝向與初始時第一次一致的時刻,即
時間複雜度 。
#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 main() {
int n; read(n);
cout << 360 / __gcd(n, 360) << endl;
return 0;
}
Problem B. Extension
記 ,表示 時,問題的答案。
則顯然有
對於 ,考慮枚舉第一行第二個黑色的格子的位置,應當有
用部分和優化轉移,可以做到均攤 。
時間複雜度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e3 + 5;
const int P = 998244353;
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 dp[MAXN][MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int main() {
int a, b, c, d; read(a), read(b), read(c), read(d);
dp[a][b] = 1;
for (int j = b + 1; j <= d; j++)
dp[a][j] = 1ll * dp[a][j - 1] * a % P;
for (int i = a + 1; i <= c; i++) {
int sum = 0;
for (int j = b; j <= d; j++) {
update(dp[i][j], 1ll * dp[i - 1][j] * j % P);
update(dp[i][j], sum);
sum = 1ll * sum * i % P;
update(sum, 1ll * dp[i - 1][j] * j % P);
}
}
cout << dp[c][d] << endl;
return 0;
}
Problem C. Shift
將字符串中的 看做分隔符,記序列 表示相鄰的兩個 之間 的個數。
則一次操作相當於選擇 ,令 。
由於合法的序列 與合法的字符串一一對應,考慮對序列 計數。
則記 表示決定了 ,其相較於原字符串總的增加量爲 ,進行了 次操作的方案數,顯然有 轉移,則可以得到一個 的做法。
用部分和優化轉移,時間複雜度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 305;
const int P = 998244353;
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 n, l, m, a[MAXN];
int dp[MAXN][MAXN][MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int main() {
scanf("\n%s%d", s + 1, &m);
int last = 0; l = strlen(s + 1), chkmin(m, l);
for (int i = 1; i <= l + 1; i++)
if (s[i] != '1') {
a[++n] = i - last - 1;
last = i;
}
dp[0][0][0] = 1;
for (int i = 1; i <= n; i++) {
static int b[MAXN][MAXN], c[MAXN][MAXN];
memset(b, 0, sizeof(b));
memset(c, 0, sizeof(c));
for (int j = 0; j <= l; j++)
for (int k = 0; k <= m; k++) {
int tmp = dp[i - 1][j][k];
if (tmp == 0) continue;
update(b[j - min(a[i], j)][k], tmp);
update(b[j + 1][k], P - tmp);
update(c[j + 1][k + 1], tmp);
}
for (int j = 0; j <= l; j++)
for (int k = 0; k <= m; k++) {
if (j != 0) update(b[j][k], b[j - 1][k]);
update(dp[i][j][k], b[j][k]);
}
for (int j = 0; j <= l; j++)
for (int k = 0; k <= m; k++) {
if (j != 0 && k != 0) update(c[j][k], c[j - 1][k - 1]);
update(dp[i][j][k], c[j][k]);
}
}
int ans = 0;
for (int i = 0; i <= m; i++)
update(ans, dp[n][0][i]);
cout << ans << endl;
return 0;
}
Problem D. Secret Passage
考慮如何判斷一個給定的字符串是否可以被生成。
令所判斷的字符串爲 ,考慮 的最後一個字符:
若最後一個字符相同,則 能夠生成 當且僅當刪去一個字符的 可以生成刪去一個字符的 。
若最後一個字符不同,則必須要通過給定的操作在此處插入一個字符。
對此判定過程設計狀態 ,表示當前 ,需要通過操作插入 個 , 個 。
設計動態規劃對各個狀態的個數進行計數,顯然有 轉移。
考慮如何判斷狀態 是否對應着可以生成的字符串 。則我們要求在僅對字符串的前 位進行操作的情況下,可以向後插入 個 , 個 。
對於 中的前 個字符,我們可以選擇進行一次操作,將其中一個字符插入到後面,也可以選則將任意一個字符插入到前 個字符中,有了這樣的字符,我們便可以選擇操作 中的第 個字符和一個被事先插入的字符了。
因此,記 表示對 的前 個字符進行操作,可以向後插入 個 , 個 的情況下,還可以向前 個字符中至多可以插入的字符數,同樣有 轉移。
時間複雜度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 305;
const int P = 998244353;
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 n;
int dp[MAXN][MAXN][MAXN];
int vis[MAXN][MAXN][MAXN];
bool res[MAXN][MAXN][MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
bool check(int x, int y, int z) {
return res[x][y][z];
}
int main() {
scanf("%s", s + 1), n = strlen(s + 1), dp[n][0][0] = 1;
for (int i = n; i >= 1; i--)
for (int j = 0; j + (n - i) <= n - 1; j++)
for (int k = 0; j + k + (n - i) <= n - 1; k++)
if (s[i] == '0') {
update(dp[i - 1][j][k], dp[i][j][k]);
update(dp[i][j][k + 1], dp[i][j][k]);
} else {
update(dp[i - 1][j][k], dp[i][j][k]);
update(dp[i][j + 1][k], dp[i][j][k]);
}
memset(vis, -1, sizeof(vis));
vis[0][0][0] = 0;
for (int i = 0; i <= n; i++)
for (int j = 0; j <= i / 2; j++)
for (int k = 0; j + k <= i / 2; k++) {
if (vis[i][j][k] == -1) continue;
int tmp = vis[i][j][k];
if (i + 1 <= n && tmp != 0) {
if (s[i + 1] == '0') chkmax(vis[i + 1][j + 1][k], tmp - 1);
if (s[i + 1] == '1') chkmax(vis[i + 1][j][k + 1], tmp - 1);
}
if (i + 2 <= n) {
if (s[i + 1] == '0' || s[i + 2] == '0') chkmax(vis[i + 2][j + 1][k], tmp);
if (s[i + 1] == '1' || s[i + 2] == '1') chkmax(vis[i + 2][j][k + 1], tmp);
chkmax(vis[i + 2][j][k], tmp + 1);
}
}
for (int i = 0; i <= n; i++)
for (int j = n; j >= 0; j--)
for (int k = n; k >= 0; k--) {
res[i][j][k] = vis[i][j][k] != -1;
if (i != 0) res[i][j][k] |= res[i - 1][j][k];
res[i][j][k] |= res[i][j + 1][k];
res[i][j][k] |= res[i][j][k + 1];
}
int ans = 0;
for (int i = 0; i <= n; i++)
for (int j = 0; j + (n - i) <= n; j++)
for (int k = 0; j + k + (n - i) <= n; k++)
if (dp[i][j][k] && (n - i) + j + k != 0) {
if (check(i, j, k)) {
update(ans, dp[i][j][k]);
}
}
cout << ans << endl;
return 0;
}
Problem E. Permutation Cover
考慮如何判斷答案是否爲 。
則可以發現,答案爲 當且僅當
對不不滿足以上條件的情況,考慮出現次數最少的元素,不難構造一種方案。
由於我們需要確定字典序最小的解,我們還需要能夠判定以給定排列 開頭的的方案是否存在。那麼,令 表示剩餘各個元素的出現次數,若
則顯然是存在方案的,而相比於剛開始的判定問題,不同的是,若
也是有可能存在方案的。
進一步地,此時,存在方案當且僅當在 中,使得 取到最大的 均出現在使得 取到最小的 的前面。由此,枚舉接在 後的新排列的長度,我們可以貪心地找到能夠拼接上去的,字典序最小的序列。
時間複雜度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
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, m, s, a[MAXN];
int x, y, cur[MAXN], res[MAXN], ans[MAXN];
bool cnp(int s, int t) {
if ((a[s] == x) == (a[t] == x)) return s < t;
else return (a[s] == x) < (a[t] == x);
}
void work(int len) {
bool valid = true, found = false;
for (int i = 1; i <= len; i++)
a[cur[i]]--;
int Min = a[1], Max = a[1];
for (int i = 2; i <= n; i++) {
chkmin(Min, a[i]);
chkmax(Max, a[i]);
}
if (Min * 2 >= Max) {
found = true;
for (int i = 1; i <= len; i++)
res[i] = cur[i];
sort(res + 1, res + len + 1);
} else if (Min * 2 + 1 == Max) {
x = Min, y = Max;
int k1 = 0, k2 = 0, k = 0;
static int res1[MAXN], res2[MAXN];
for (int i = 1; i <= len; i++)
if (a[cur[i]] == Min || a[cur[i]] == Max) res1[++k1] = cur[i];
else res2[++k2] = cur[i];
sort(res1 + 1, res1 + k1 + 1, cnp);
sort(res2 + 1, res2 + k2 + 1);
int x1 = 1, x2 = 1;
for (int i = 1; i <= len; i++)
if (x1 <= k1 && x2 <= k2) {
if (res1[x1] < res2[x2]) res[i] = res1[x1++];
else res[i] = res2[x2++];
} else {
if (x1 <= k1) res[i] = res1[x1++];
else res[i] = res2[x2++];
}
found = true;
for (int i = len + 1; i <= n; i++)
if (a[cur[i]] == Min) found = false;
found |= (x1 == 0) || (a[res1[1]] == Max);
}
for (int i = 1; i <= len; i++)
a[cur[i]]++;
if (!valid || !found) res[1] = 0;
}
bool cmp(int *a, int *b) {
int pos = 1;
while (a[pos] == b[pos]) pos++;
return a[pos] < b[pos];
}
bool check() {
int Min = a[1], Max = a[1];
for (int i = 2; i <= n; i++) {
chkmin(Min, a[i]);
chkmax(Max, a[i]);
}
return Min * 2 >= Max;
}
int main() {
read(n);
for (int i = 1; i <= n; i++) {
read(a[i]);
s += a[i];
}
if (!check()) {
puts("-1");
return 0;
}
for (int i = 1; i <= n; i++)
cur[i] = i;
work(n);
for (int i = 1; i <= n; i++) {
m++, a[res[i]]--;
cur[i] = res[i];
ans[m] = res[i];
}
while (m != s) {
static int inc[MAXN];
memset(inc, 0, sizeof(inc));
int len = 0; inc[1] = n + 1;
for (int i = 1; i <= n; i++) {
work(i);
if (res[1] != 0 && cmp(res, inc)) {
len = i;
for (int j = 1; j <= i; j++)
inc[j] = res[j];
}
}
for (int i = 1; i + len <= n; i++)
cur[i] = cur[i + len];
for (int i = 1; i <= len; i++) {
ans[++m] = inc[i];
cur[n - len + i] = inc[i], a[inc[i]]--;
}
}
for (int i = 1; i <= m; i++)
printf("%d ", ans[i]);
printf("\n");
return 0;
}
Problem F. Forbidden Tournament
可以參考問題 Codeforces 1338E JYPnation 。
引理: 重複刪去圖中入度爲 的點,剩餘的圖是強連通的。並且,對於強連通的合法競賽圖,可以找到唯一的一條哈密爾頓迴路,使得每個點的出邊均指向其在迴路上的一段後繼。
關於引理的證明可以參考官方題解。
由此,枚舉刪去圖中入度爲 的點的次數,便只需要解決對於強連通的合法競賽圖的計數問題。
固定哈密爾頓迴路,並枚舉 號節點的出度 ,記 表示當前處理到節點 ,其出度爲 ,且此時方案仍然合法的方案數,樸素的轉移是 的。
時間複雜度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 205;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
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, k, P, fac[MAXN], inv[MAXN];
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 binom(int x, int y) {
if (y > x) return 0;
else return 1ll * fac[x] * inv[y] % P * inv[x - y] % P;
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
void init(int n) {
fac[0] = 1;
for (int i = 1; i <= n; i++)
fac[i] = 1ll * fac[i - 1] * i % P;
inv[n] = power(fac[n], P - 2);
for (int i = n - 1; i >= 0; i--)
inv[i] = inv[i + 1] * (i + 1ll) % P;
}
int solve(int n, int k) {
int ans = 0;
for (int deg = 1; deg <= k && deg <= n - 2; deg++) {
static int dp[MAXN][MAXN];
if ((n - 1) - deg > k) continue;
memset(dp, 0, sizeof(dp)), dp[0][deg] = 1;
for (int i = 0; i <= deg - 1; i++)
for (int j = deg; j <= n - 1; j++) {
int tmp = dp[i][j];
for (int t = max(j, i + 2); t <= n - 1; t++) {
int tnp = t - (i + 1);
if (tnp <= k && (n - 1) - tnp <= k) update(dp[i + 1][t], tmp);
}
}
for (int i = deg; i <= n - 1; i++)
update(ans, dp[deg][i]);
}
return 1ll * ans * fac[n - 1] % P;
}
int main() {
read(n), read(k), read(P);
init(n); int ans = (k == n - 1) ? fac[n] : 0;
for (int i = 3; i <= n; i++)
update(ans, 1ll * fac[n] * inv[i] % P * solve(i, k - (n - i)) % P);
cout << ans << endl;
return 0;
}
用部分和優化轉移,時間複雜度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 205;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
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, k, P, fac[MAXN], inv[MAXN];
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 binom(int x, int y) {
if (y > x) return 0;
else return 1ll * fac[x] * inv[y] % P * inv[x - y] % P;
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
void init(int n) {
fac[0] = 1;
for (int i = 1; i <= n; i++)
fac[i] = 1ll * fac[i - 1] * i % P;
inv[n] = power(fac[n], P - 2);
for (int i = n - 1; i >= 0; i--)
inv[i] = inv[i + 1] * (i + 1ll) % P;
}
int solve(int n, int k) {
int ans = 0;
for (int deg = 1; deg <= k && deg <= n - 2; deg++) {
static int dp[MAXN][MAXN];
if ((n - 1) - deg > k) continue;
memset(dp, 0, sizeof(dp));
dp[0][deg] = 1, dp[0][deg + 1] = P - 1;
for (int i = 0; i <= deg - 1; i++)
for (int j = deg; j <= n - 1; j++) {
update(dp[i][j], dp[i][j - 1]);
int tmp = dp[i][j], l = max(max(j, i + 2), n + i - k), r = min(n - 1, k + (i + 1));
if (l <= r) update(dp[i + 1][l], tmp), update(dp[i + 1][r + 1], P - tmp);
}
for (int i = deg; i <= n - 1; i++) {
update(dp[deg][i], dp[deg][i - 1]);
update(ans, dp[deg][i]);
}
}
return 1ll * ans * fac[n - 1] % P;
}
int main() {
read(n), read(k), read(P);
init(n); int ans = (k == n - 1) ? fac[n] : 0;
for (int i = 3; i <= n; i++)
update(ans, 1ll * fac[n] * inv[i] % P * solve(i, k - (n - i)) % P);
cout << ans << endl;
return 0;
}