題目傳送門:AtCoder Regular Contest 107。
A - Simple Math
求和號的性質。時間復雜度為 \(\mathcal O (1)\)。
#include <cstdio>
typedef long long LL;
const int Mod = 998244353;
inline LL S(LL x) { return x * (x + 1) / 2 % Mod; }
int main() {
int A, B, C;
scanf("%d%d%d", &A, &B, &C);
printf("%lld\n", S(A) * S(B) % Mod * S(C) % Mod);
return 0;
}
B - Quadruple
令 \(f(x)\) 為滿足 \(1 \le a, b \le N\) 和 \(a + b = x\) 的數對 \((a, b)\) 的數量,可以 \(\mathcal O (1)\) 計算。
答案為 \(\displaystyle \sum_{x = 2}^{2 N} f(x) f(x + K)\)。時間復雜度為 \(\mathcal O (N)\)。
#include <cstdio>
typedef long long LL;
int N, K;
LL Ans;
inline int f(int x) {
return x < 2 || x > 2 * N ? 0 : x <= N + 1 ? x - 1 : 2 * N + 1 - x;
}
int main() {
scanf("%d%d", &N, &K);
for (int i = 2; i <= 2 * N; ++i)
Ans += (LL)f(i) * f(i + K);
printf("%lld\n", Ans);
return 0;
}
C - Shuffle Permutation
注意任意兩行交換不會影響列交換的條件的真假性,反之亦然。
對兩個能交換的行之間連邊,則每個連通塊內均可任意交換,貢獻 \(c!\),其中 \(c\) 為連通塊大小。
對列同樣處理。時間復雜度為 \(\mathcal O (N^3)\)。
#include <cstdio>
#include <vector>
typedef long long LL;
const int Mod = 998244353;
const int MN = 55;
int N, K, A[MN][MN], Ans;
std::vector<int> G[MN];
int c, vis[MN];
void DFS(int u) {
vis[u] = 1;
Ans = (LL)Ans * ++c % Mod;
for (int v : G[u]) if (!vis[v]) DFS(v);
}
int main() {
scanf("%d%d", &N, &K), Ans = 1;
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= N; ++j)
scanf("%d", &A[i][j]);
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= N; ++j) if (i < j) {
int ok = 1;
for (int k = 1; k <= N; ++k)
if (A[i][k] + A[j][k] > K)
ok = 0;
if (ok)
G[i].push_back(j),
G[j].push_back(i);
}
for (int i = 1; i <= N; ++i) if (!vis[i]) c = 0, DFS(i);
for (int i = 1; i <= N; ++i) G[i].clear(), vis[i] = 0;
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= N; ++j) if (i < j) {
int ok = 1;
for (int k = 1; k <= N; ++k)
if (A[k][i] + A[k][j] > K)
ok = 0;
if (ok)
G[i].push_back(j),
G[j].push_back(i);
}
for (int i = 1; i <= N; ++i) if (!vis[i]) c = 0, DFS(i);
printf("%d\n", Ans);
return 0;
}
D - Number of Multisets
也就是初始時 \({0.5}^0\) 處有 \(K\) 個石子,可以把其中 \(x\) 個移到 \({0.5}^1\) 處,變成 \(2 x\) 個,然后還可以繼續移到更多的 \({0.5}^p\) 處。
最終正好變成 \(N\) 個石子,也就是恰好分裂 \(N - K\) 次。
令 \(\operatorname{dp}(i, j)\) 表示恰好分裂 \(i\) 次,且現在位置有 \(j\) 個石子時的方案數。答案即是 \(\operatorname{dp}(N - K, K)\)。
如果 \(i \ge j \ge 1\),則有 \(\displaystyle \operatorname{dp}(i, j) = 1 + \sum_{k = 1}^{j} \operatorname{dp}(i - k, 2 k) = \operatorname{dp}(i, j - 1) + \operatorname{dp}(i - j, 2 j)\)。
對於 \(i < j\) 的情況,有 \(\operatorname{dp}(i, j) = \operatorname{dp}(i, i)\)。
也就是 \(\operatorname{dp}(i, j) = \operatorname{dp}(i, j - 1) + \operatorname{dp}(i - j, \min \{ 2 j, i - j \})\)。
邊界條件為 \(\operatorname{dp}(i, 0) = [i = 0]\)。時間復雜度為 \(\mathcal O (N^2)\)。
#include <cstdio>
#include <algorithm>
typedef long long LL;
const int Mod = 998244353;
const int MN = 3005;
int N, K, f[MN][MN];
int main() {
scanf("%d%d", &N, &K), N -= K;
f[0][0] = 1;
for (int i = 1; i <= N; ++i) {
f[i][0] = 0;
for (int j = 1; j <= i; ++j)
f[i][j] = (f[i][j - 1] + f[i - j][std::min(i - j, 2 * j)]) % Mod;
}
printf("%d\n", f[N][std::min(N, K)]);
return 0;
}
E - Mex Mat
考察一個簡單一些的情況,如果把 \(1, 2\) 都看作 \(1\),運算規則是類似的,都是有 \(0\) 參與則結果非 \(0\),否則為 \(0\)。
此時考察一個位置 \(a_{i, j}\),其中 \(i, j\) 足夠大,然后考察 \(a_{i + 1, j + 1}\) 與其的關系:
- 如果 \(a_{i, j} = 0\),則 \(a_{i, j + 1}, a_{i + 1, j}\) 均為 \(1\),於是 \(a_{i + 1, j + 1} = 0\)。
- 如果 \(a_{i, j} = 1\),則如果 \(a_{i + 1, j + 1} = 0\) 則 \(a_{i, j + 1}, a_{i + 1, j}\) 均為 \(1\),於是 \(a_{i - 1, j + 1}, a_{i + 1, j - 1}\) 均為 \(0\),於是 \(a_{i - 1, j}, a_{i, j - 1}\) 均為 \(1\),於是 \(a_{i, j} = 0\),但已知 \(a_{i, j} = 1\),導出矛盾。所以 \(a_{i + 1, j + 1} = 1\)。
也就是有 \(a_{i, j} = a_{i + 1, j + 1}\)。那么其中的 \(i, j\) 足夠大是多大呢?其實是 \(i, j \ge 3\) 即可,因為上述論證中只涉及 \(i - 1\) 和 \(j - 1\) 的自由轉移,於是就是 \((i - 1), (j - 1) \ge 2\),所以 \(i, j \ge 3\)。
對於引入了 \(0, 1, 2\) 三種值的情況是類似的:
- 如果 \(a_{i, j} = 0\),則 \(a_{i, j + 1}, a_{i + 1, j}\) 均非 \(0\),於是 \(a_{i + 1, j + 1} = 0\)。
- 如果 \(a_{i, j} = 1\),則 \(a_{i, j + 1}, a_{i + 1, j}\) 均非 \(1\),即為 \(0\) 或 \(2\),所以 \(a_{i + 1, j + 1}\) 為 \(0\) 或 \(1\),當 \(a_{i + 1, j + 1} = 0\) 時只有可能是 \(a_{i, j + 1}, a_{i + 1, j}\) 均為 \(2\),於是 \(a_{i - 1, j + 1}, a_{i + 1, j - 1}\) 均為 \(0\),於是 \(a_{i - 1, j}, a_{i, j - 1}\) 均非 \(0\),於是 \(a_{i, j} = 0\),但已知 \(a_{i, j} = 1\),導出矛盾。所以 \(a_{i + 1, j + 1} = 1\)。
- 如果 \(a_{i, j} = 2\),則 \(a_{i - 1, j}, a_{i, j - 1}\) 中恰有一者為 \(1\) 而另一者為 \(0\),不失一般性,不妨令 \(a_{i - 1, j} = 0\) 而 \(a_{i, j - 1} = 1\),則根據「若 \(a_{i, j} = 0\) 則 \(a_{i + 1, j + 1} = 0\)」有 \(a_{i, j + 1} = 0\),又根據「若 \(a_{i, j} = 1\) 則 \(a_{i + 1, j + 1} = 1\)」有 \(a_{i + 1, j} = 1\),於是 \(a_{i + 1, j + 1} = 2\)。
此時必須滿足 \(i, j \ge 4\),因為對「若 \(a_{i, j} = 2\) 則 \(a_{i + 1, j + 1} = 2\)」的證明中用到了對 \(a_{i - 1, j}\) 或 \(a_{i, j - 1}\) 施以的「當 \(i, j \ge 3\) 時,若 \(a_{i, j} = 1\) 則 \(a_{i + 1, j + 1} = 1\)」結論,所以對 \(i, j\) 的限制再提高 \(1\)。
只需算出所有 \(\min(i, j) \le 4\) 的 \(a_{i, j}\) 值即可,后續的值根據 \(\min(i, j) = 4\) 的那些 \(a_{i, j}\) 直接得到。
時間復雜度為 \(\mathcal O (N)\)。
#include <cstdio>
#include <vector>
inline int Mex(int x, int y) {
if (x && y) return 0;
if (x + y == 1) return 2;
return 1;
}
typedef long long LL;
const int MN = 500005;
int N;
std::vector<int> A[MN];
LL Ans[3];
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i)
A[i].resize((i <= 4 ? N : 4) + 1);
for (int i = 1; i <= N; ++i) scanf("%d", &A[1][i]);
for (int i = 2; i <= N; ++i) scanf("%d", &A[i][1]);
for (int i = 2; i <= N; ++i) {
int lim = i <= 4 ? N : 4;
for (int j = 2; j <= lim; ++j)
A[i][j] = Mex(A[i - 1][j], A[i][j - 1]);
}
for (int i = 1; i <= N; ++i) {
int lim = i <= 4 ? N : 4;
for (int j = 1; j <= lim; ++j)
++Ans[A[i][j]];
}
if (N > 4) {
for (int i = 4; i < N; ++i)
Ans[A[4][i]] += N - i;
for (int i = 5; i < N; ++i)
Ans[A[i][4]] += N - i;
}
printf("%lld %lld %lld\n", Ans[0], Ans[1], Ans[2]);
return 0;
}
F - Sum of Abs
這題一看就很網絡流,但是建圖並不傳統,比較神奇的最小割建圖方式。
首先需要一步重要的轉化:對整個連通塊的取絕對值變成給每個未被刪除的結點賦權 \(+1\) 或 \(-1\),要求通過邊直接相連的兩點的權值必須相同。這就表達了連通塊符號必須相同的意思,並且留出了連通塊權值可以為負的回旋余地(Note:最大化套個絕對值的東西可以把絕對值拆掉不影響結果,可能更方便進一步轉化)。
為了使用類文理分科的最小割建圖,我們自然是要把所有可能的貢獻先加起來,然后減去最小割代表的付費限制。
即令 \(\displaystyle \mathrm{Ans} = \sum_{i = 1}^{N} |B_i|\),然后最終答案為 \(\mathrm{Ans} \gets \mathrm{Ans} - \operatorname{MinCut}(G)\)。
我們需要描述一個結點的三種狀態,即被刪除、\(+1\) 權、和 \(-1\) 權。
然后還要處理如果一條邊相連的兩個點權值分別為 \(+1\) 和 \(-1\) 則方案非法的條件。
考慮如下建圖,拆點 \(u\) 為 \(u_1, u_2\):\(\langle \mathrm{Source} \rangle \to u_1 \to u_2 \to \langle \mathrm{Sink} \rangle\),割開這三條邊的含義分別為 \(+1\) 權、被刪除、和 \(-1\) 權。
於是邊權為:
- 如果 \(B_u \ge 0\),則權值分別為 \(0\)、\(A_u + B_u\)、和 \(2 B_u\)。
- 如果 \(B_u < 0\),則權值分別為 \(-2 B_u\)、\(A_u - B_u\)、和 \(0\)。
對於一條邊連接的兩個點 \(u, v\),連接 \(u_2 \to v_1\) 和 \(v_2 \to u_1\),權值均為 \(+\infty\)。
此時如果割 \(\langle \mathrm{Source} \rangle \to u_1\) 和 \(v_2 \to \langle \mathrm{Sink} \rangle\) 則有路徑 \(\langle \mathrm{Source} \rangle \to v_1 \to v_2 \to u_1 \to u_2 \to \langle \mathrm{Sink} \rangle\) 從而不合法。
時間復雜度為 \(\operatorname{Dinic}(|V| = 2 N + 2, |E| = 2 (N + M)) = \mathcal O (N^2 (N + M))\)。
#include <cstdio>
#include <algorithm>
namespace DinicFlows {
const int Inf = 0x3f3f3f3f;
const int MN = 605, MM = 1205;
int N, S, T;
int h[MN], iter[MN], nxt[MM * 2], to[MM * 2], w[MM * 2], tot;
inline void SetST(int _S, int _T) { S = _S, T = _T; }
inline void Init(int _N) {
N = _N, tot = 1;
for (int i = 1; i <= N; ++i) h[i] = 0;
SetST(_N - 1, _N);
}
inline void ins(int u, int v, int x) {
if (tot + 1 >= MM * 2) { puts("Error : too many edges."); return ; }
nxt[++tot] = h[u], to[tot] = v, w[tot] = x, h[u] = tot;
}
inline void insw(int u, int v, int w1 = Inf, int w2 = 0) {
if (!u) u = S; if (!v) v = T;
ins(u, v, w1), ins(v, u, w2);
}
int lv[MN], que[MN], l, r;
inline bool Lvl() {
for (int i = 1; i <= N; ++i) lv[i] = 0;
lv[S] = 1;
que[l = r = 1] = S;
while (l <= r) {
int u = que[l++];
for (int i = h[u]; i; i = nxt[i])
if (w[i] && !lv[to[i]]) {
lv[to[i]] = lv[u] + 1;
que[++r] = to[i];
}
}
return lv[T] != 0;
}
int Flow(int u, int f) {
if (u == T) return f;
int d = 0, s = 0;
for (int &i = iter[u]; i; i = nxt[i])
if (w[i] && lv[to[i]] == lv[u] + 1) {
d = Flow(to[i], std::min(f, w[i]));
f -= d, s += d;
w[i] -= d, w[i ^ 1] += d;
if (!f) break;
}
return s;
}
inline int Dinic() {
int Ans = 0;
while (Lvl()) {
for (int i = 1; i <= N; ++i) iter[i] = h[i];
Ans += Flow(S, Inf);
}
return Ans;
}
}
const int MN = 305;
int N, M, A[MN], B[MN], Ans;
int main() {
scanf("%d%d", &N, &M);
for (int i = 1; i <= N; ++i) scanf("%d", &A[i]);
for (int i = 1; i <= N; ++i) scanf("%d", &B[i]);
DinicFlows::Init(2 * N + 2);
for (int i = 1; i <= N; ++i) {
if (B[i] >= 0) {
Ans += B[i];
DinicFlows::insw(i, N + i, A[i] + B[i]);
DinicFlows::insw(N + i, 0, 2 * B[i]);
} else {
Ans -= B[i];
DinicFlows::insw(0, i, 2 * -B[i]);
DinicFlows::insw(i, N + i, A[i] - B[i]);
}
}
for (int i = 1; i <= M; ++i) {
int x, y;
scanf("%d%d", &x, &y);
DinicFlows::insw(N + x, y);
DinicFlows::insw(N + y, x);
}
printf("%d\n", Ans - DinicFlows::Dinic());
return 0;
}