題目傳送門:AtCoder Regular Contest 108。
A - Sum and Product
要求 \(x + y = S\) 且 \(x \times y = P\),枚舉 \(P\) 的所有因數進行檢查。時間復雜度為 \(\mathcal O (\sqrt{M})\)。
#include <cstdio>
typedef long long LL;
LL N, M;
int main() {
scanf("%lld%lld", &N, &M);
for (LL x = 1; x * x <= M; ++x) if (M % x == 0)
if (x + M / x == N) return puts("Yes"), 0;
puts("No");
return 0;
}
B - Abbreviate Fox
維護一個棧,每當棧頂連續三個是 fox
就彈棧三次。時間復雜度為 \(\mathcal O (N)\)。
#include <cstdio>
const int MN = 200005;
int N, tp;
char stk[MN];
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i) {
char s[3];
scanf("%1s", s);
stk[++tp] = *s;
if (tp >= 3 && stk[tp - 2] == 'f' && stk[tp - 1] == 'o' && stk[tp] == 'x') tp -= 3;
}
printf("%d\n", tp);
return 0;
}
C - Keep Graph Connected
考察一棵生成樹,僅需對樹構造解即可。同時也顯示了沒有無解的情況。
隨意欽點一個點為根,把它的權值設為任意值,然后自頂向下賦權。
如果該點的雙親結點的權值與連接它們的邊權相同則該點賦一個與其不同的權,否則賦該邊邊權。
顯然樹中所有邊均得到保留。時間復雜度為 \(\mathcal O (N + M)\)。
#include <cstdio>
#include <vector>
const int MN = 100005, MM = 200005;
int N, M;
int eu[MM], ev[MM], ew[MM];
std::vector<int> G[MN];
int col[MN];
void DFS(int u) {
for (int i : G[u]) {
int v = u ^ eu[i] ^ ev[i];
if (col[v]) continue;
col[v] = ew[i];
if (col[v] == col[u])
col[v] = col[v] % N + 1;
DFS(v);
}
}
int main() {
scanf("%d%d", &N, &M);
for (int i = 1; i <= M; ++i) {
scanf("%d%d%d", &eu[i], &ev[i], &ew[i]);
G[eu[i]].push_back(i);
G[ev[i]].push_back(i);
}
col[1] = 1, DFS(1);
for (int i = 1; i <= N; ++i) printf("%d\n", col[i]);
return 0;
}
D - AB
只有 \(16\) 種不同的輸入,寫個暴力找規律即可。有三種分類:
其中一種是全 \(1\),一種是除了 \(N = 2\) 外全 \(2^{N - 3}\),一種是 \(\mathrm{Fibonacci}[N - 3]\)。時間復雜度為 \(\mathcal O (N)\) 或 \(\mathcal O (\log N)\)。
#include <cstdio>
const int Mod = 1000000007;
int N;
char AA[3], AB[3], BA[3], BB[3];
void pow2() {
int n = N - 3;
int res = 1;
for (int i = 1; i <= n; ++i)
res = res * 2 % Mod;
printf("%d\n", res);
}
void fib() {
int n = N - 3;
int a1 = 1, a2 = 1;
for (int i = 1; i <= n; ++i) {
int tmp = (a1 + a2) % Mod;
a1 = a2, a2 = tmp;
}
printf("%d\n", a2);
}
int main() {
scanf("%d%1s%1s%1s%1s", &N, AA, AB, BA, BB);
int s = (*AA == 'B') << 3 | (*AB == 'B') << 2 | (*BA == 'B') << 1 | (*BB == 'B');
if (N <= 3) return puts("1"), 0;
if (s == 4 || s == 10 || s == 11 || s == 12) pow2();
else if (s == 6 || s == 8 || s == 9 || s == 14) fib();
else puts("1");
return 0;
}
E - Random IS
補充 \(a_0 = 0\) 以及 \(a_{N + 1} = N + 1\)。並且假設現在欽點選中了 \(0\) 和 \(N + 1\) 這兩個位置上的數。
考慮 \(\operatorname{dp}(i, j)\) 表示只考慮 \(i \sim j\) 之間的元素,欽點選中了 \(a_i\) 和 \(a_j\),其他都還沒選,問最終期望選多少個(不包括 \(a_i\) 和 \(a_j\))。
則答案即為 \(\operatorname{dp}(0, N + 1)\)。考慮類似區間 DP 的轉移,即枚舉中間第一次選擇了 \(a_k\)。但是兩側如何合並?
實際上兩側的貢獻可以看作獨立。因為雖然概率會互相影響,但是僅關心每一部分內的相對概率已經足夠了,因為兩側具體選擇了什么不會產生影響,影響的只有概率,但是概率只需考慮每側相對的即可。所以有:
其中 \(c\) 為合法的 \(k\) 的數量,必須至少為 \(1\)。如果 \(c = 0\) 則 \(\operatorname{dp}(i, j) = 0\)。
所以我們可以分別考慮 \(\displaystyle \sum_{k} \operatorname{dp}(i, k)\) 和 \(\displaystyle \sum_{k} \operatorname{dp}(k, j)\),然而它們其實是對稱的。
也就是,我們可以按照 \(j - i\) 遞增計算(正常的區間 DP 都是如此)。然后
對於一個 \((i, j)\) 僅需要計算滿足 \(a_k < a_j\) 的 \(\operatorname{dp}(i, k)\) 之和,這可以使用樹狀數組維護。
對於 \(c\) 的計算可以使用二維前綴和。時間復雜度為 \(\mathcal O (N^2 \log N)\)。
#include <cstdio>
typedef long long LL;
const int Mod = 1000000007;
const int MN = 2005;
int N, A[MN], S[MN][MN], Inv[MN];
int bit1[MN][MN], bit2[MN][MN];
inline void Add(int *b, int i, int x) {
for (; i <= N; i += i & -i) b[i] -= Mod - x, b[i] += b[i] >> 31 & Mod;
}
inline int Qur(int *b, int i) {
int s = 0;
for (; i; i -= i & -i) s -= Mod - b[i], s += s >> 31 & Mod;
return s;
}
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i) {
for (int j = 1; j <= N; ++j) S[i][j] = S[i - 1][j];
scanf("%d", &A[i]);
for (int j = A[i]; j <= N; ++j) ++S[i][j];
}
Inv[1] = 1;
for (int i = 2; i <= N; ++i) Inv[i] = (LL)(Mod - Mod / i) * Inv[Mod % i] % Mod;
A[0] = 0, A[N + 1] = N + 1;
for (int dif = 2; dif <= N + 2; ++dif) {
for (int i = 0; i + dif <= N + 1; ++i) {
int j = i + dif;
if (A[i] > A[j]) continue;
int c = S[j - 1][A[j] - 1] - S[j - 1][A[i]] - S[i][A[j] - 1] + S[i][A[i]];
if (!c) continue;
int v = ((LL)(Qur(bit1[i], A[j] - 1) + Qur(bit2[j], N - A[i])) * Inv[c] + 1) % Mod;
if (j <= N) Add(bit1[i], A[j], v);
if (i >= 1) Add(bit2[j], N - A[i] + 1, v);
if (i == 0 && j == N + 1) printf("%d\n", v);
}
}
return 0;
}
F - Paint Tree
考察這棵樹的某一條直徑,假設其兩端點分別為 \(u_1\) 和 \(u_2\)。欽點 \(u_1\) 的顏色為黑色,最后答案乘 \(2\) 即可。
如果 \(u_2\) 的顏色與 \(u_1\) 相同則 niceness 為直徑長度,向答案貢獻 \(2^{N - 2}\) 倍。
如果 \(u_2\) 的顏色與 \(u_1\) 不同則易證 niceness 一定是 \(u_1\) 與某個點或 \(u_2\) 與某個點之間的距離。
處理出其他 \(N - 2\) 個點與 \(u_1\) 和 \(u_2\) 之間的距離,相當於每個點選擇其一進行染色,niceness 為其中最大值。
要計算 niceness 之和,考慮:
所以答案(乘 \(2\) 前)為 \(\displaystyle (N + \mathrm{diameter}) 2^{N - 2} - \sum_{x = 1}^{N} \# [\mathrm{niceness} < x]\)。
要計算后者,注意到每個結點獨立,方案數為 \(0\) 或 \(1\) 或 \(2\)。
可以把 \(\operatorname{dis}(v, u_1)\) 和 \(\operatorname{dis}(v, u_2)\) 算一下,記在桶里拉個鏈,然后掃描線處理,見代碼。時間復雜度為 \(\mathcal O (N)\)。
#include <cstdio>
#include <algorithm>
#include <vector>
typedef long long LL;
const int Mod = 1000000007;
const int MN = 200005;
int N, pw2[MN];
std::vector<int> G[MN], V[MN];
int dep[MN], dep1[MN], dep2[MN];
void DFS(int u, int p) {
dep[u] = dep[p] + 1;
for (int v : G[u]) if (v != p) DFS(v, u);
}
int c[MN];
int main() {
dep[0] = -1, pw2[0] = 1;
scanf("%d", &N);
for (int i = 1; i <= N; ++i) pw2[i] = pw2[i - 1] * 2 % Mod;
for (int i = 1, x, y; i < N; ++i)
scanf("%d%d", &x, &y),
G[x].push_back(y),
G[y].push_back(x);
DFS(1, 0);
int u1 = std::max_element(dep + 1, dep + N + 1) - dep;
DFS(u1, 0);
for (int i = 1; i <= N; ++i) dep1[i] = dep[i];
int u2 = std::max_element(dep + 1, dep + N + 1) - dep;
DFS(u2, 0);
for (int i = 1; i <= N; ++i) dep2[i] = dep[i];
for (int i = 1; i <= N; ++i) if (i != u1 && i != u2)
V[dep1[i]].push_back(i),
V[dep2[i]].push_back(i);
int Ans = (LL)pw2[N - 2] * (dep1[u2] + N) % Mod;
int Val = 1, cnt = N - 2;
for (int i = 1; i <= N; ++i) {
if (!cnt) Ans = (Ans - Val + Mod) % Mod;
for (int u : V[i]) {
if (c[u]) Val = Val * 2 % Mod;
else --cnt;
++c[u];
}
}
printf("%d\n", Ans * 2 % Mod);
return 0;
}