# 2024 YCPC 解 by SorahISA
- [Contest Link](https://codeforces.com/contestInvitation/518b22b971a57def992bb054497007aa839488de)
- [Scoreboard](https://codeforces.com/spectator/ranklist/1032166ab4503d6e76067f596f25113c)
## Problem A. Trip Counting I <span class="medium pill">Medium</span>
> ###### Problem Setter: hank55663
補圖上數 $C_3$。
原圖上三個點如果有 $k$ 條邊 $\implies$ 補圖上有 $3-k$ 條邊。
- 有 $A_0 = \binom{n}{3}$ 個點 tuple 至少有 $0$ 條邊(任三點);
- 有 $A_1 = m \cdot (n-2)$ 個點 tuple 至少有 $1$ 條邊(每條邊都往外找任意一個點);
- 有 $A_2 = \sum_{i=1}^{n} \binom{\text{deg}(i)}{2}$ 個點 tuple 至少有 $2$ 條邊;
- 有 $A_3 = \# C_3$ 個點 tuple 是 $C_3$。
答案就是 $3! \cdot (A_0 - 3 A_1 + 3 A_2 - A_3)$。
https://github.com/OmeletWithoutEgg/ckiseki/blob/master/codes/Graph/CountCycles.cpp
TC:$\mathcal{O}(m \log m)$。
## Problem B. Trip Counting II <span class="hard pill">Hard</span>
> ###### Problem Setter: hank55663
數 $C_4$。
https://github.com/OmeletWithoutEgg/ckiseki/blob/master/codes/Graph/CountCycles.cpp
TC:$\mathcal{O}(m \log m)$。
## Problem C. Trip Counting III <span class="easy-medium pill">Easy Medium</span>
> ###### Problem Setter: hank55663
數 $C_5$。
注意 $n$ 跟 $m$ 很小,亂做就好。看起來大家都沒有通過閱讀測驗。
我的做法是枚舉三條相鄰邊 $(a, b), (b, c), (c, d)$ 然後計算 $a$ 跟 $d$ 共同鄰居扣掉 $b$ 跟 $c$ 的數量。
TC:$\mathcal{O}(m^3)$。
:::spoiler Code
```cpp=
void solve() {
int N, M; cin >> N >> M;
vector<pii> edges(M);
vector<bitset<maxn>> adj_mat(N+1);
for (auto &[u, v] : edges) {
cin >> u >> v;
adj_mat[u][v] = adj_mat[v][u] = 1;
}
vector cap(N+1, vector<int>(N+1, 0));
for (int i = 1; i <= N; ++i) for (int j = 1; j <= N; ++j) cap[i][j] = (adj_mat[i] & adj_mat[j]).count();
ll ans = 0;
for (int i = 0; i < M; ++i) for (int j = 0; j < M; ++j) for (int k = 0; k < M; ++k) {
if (i == j or i == k or j == k) continue;
auto [i1, i2] = edges[i];
auto [j1, j2] = edges[j];
auto [k1, k2] = edges[k];
if (i1 == j1) swap(i1, i2);
if (i2 == j2) swap(j1, j2);
if (i1 == j2) swap(i1, i2), swap(j1, j2);
if (j1 == k1) swap(j1, j2);
if (j2 == k2) swap(k1, k2);
if (i2 == j1 and j2 == k1 and i1 != k2) {
ans += cap[i1][k2] - adj_mat[i2][k2] - adj_mat[i1][k1];
}
}
cout << ans << "\n";
}
```
:::
## Problem D. Rearrangement <span class="easy pill">Easy</span>
> ###### Problem Setter: henotrix
經典 greedy 題,每個 column 由小排到大就是最佳解。
證明可以用 exchange argument 證。
:::spoiler Code
```cpp=
void solve() {
int N, M; cin >> N >> M;
vector arr(M, vector<int>(N, 0));
for (int i = 0; i < N; ++i) {
for (int j = 0; j < M; ++j) cin >> arr[j][i];
}
for (int j = 0; j < M; ++j) sort(ALL(arr[j]));
int ans = 0;
for (int i = 0; i < N; ++i) {
for (int j = 0; j+1 < M; ++j) {
ans += abs(arr[j][i] - arr[j+1][i]);
}
}
cout << ans << "\n";
}
```
:::
## Problem E. Elimination Game <span class="easy pill">Easy</span>
> ###### Problem Setter: henotrix
觀察一下應該就能發現只要不是 $a = 0$ 或 $b = 0$ 就能讓任何數字留下來。
$a = 0$ 就只可能留最大的,vice versa。
:::spoiler Code
```cpp=
void solve() {
int N, A, B, X; cin >> N >> A >> B >> X;
if (A == N-1 and X != 1) cout << "No" << "\n";
else if (B == N-1 and X != N) cout << "No" << "\n";
else cout << "Yes" << "\n";
}
```
:::
## Problem F. Destroying Monsters <span class="easy-medium pill">Easy Medium</span>
> ###### Problem Setter: henotrix
經典 greedy,把 gun 依照左界排序。
:::spoiler Code
```cpp=
void solve() {
int n, m;
cin >> n >> m;
vector<int> X(n);
for (auto &x: X) cin >> x;
vector<pair<int,int>> G(m);
for (auto &[l,r]: G) cin >> l >> r;
sort(ALL(X));
sort(ALL(G), [&](auto a, auto b) {
return a.first < b.first;
});
bool can = true;
int ans = 0;
for (int i = 0, j = 0; i < n; ) {
int mx = -1;
while (j < m and G[j].first <= X[i]) {
mx = max(mx, G[j].second);
j++;
}
if (mx < X[i]) {
can = false;
break;
}
ans++;
while (i < n and X[i] <= mx)
i++;
}
if (not can) ans = -1;
cout << ans << endl;
}
```
:::
## Problem G. Graph Coloring Problem <span class="easy-medium pill">Easy Medium</span>
> ###### Problem Setter: henotrix
看限制可以大概感覺的出來要把邊由小到大加進去離線回答詢問。
只要一個 DSU 來維護最大連通塊大小就好了。
:::spoiler Code
```cpp=
struct Qry {
int id, k, ans;
};
struct Edge {
int u, v, w;
};
struct DSU {
vector<int> ary;
int mx = 1;
DSU(int n): ary(n, -1) {}
int find(int x) { return ary[x] < 0 ? x : ary[x] = find(ary[x]); }
bool merge(int u, int v) {
debug(u, v);
u = find(u), v = find(v);
if (u == v) return false;
if (-ary[u] < -ary[v]) swap(u, v);
ary[u] += ary[v];
mx = max(mx, -ary[u]);
debug(mx);
ary[v] = u;
return true;
}
int mxsz() {
return mx;
}
};
signed main() {
fast;
int n, m, Q;
cin >> n >> m >> Q;
vector<Edge> edges(m);
for (auto &[u,v,w]: edges) {
cin >> u >> v >> w;
u--; v--;
}
vector<Qry> qry(Q);
for (int q = 0; q < Q; q++) {
qry[q].id = q;
cin >> qry[q].k;
}
sort(ALL(qry), [&](auto& x, auto& y) {
return x.k < y.k;
});
sort(ALL(edges), [&](auto& x, auto& y) {
return x.w < y.w;
});
DSU dsu(n);
for (int q = 0, e = 0; q < Q; q++) {
while (e < m and edges[e].w <= qry[q].k) {
dsu.merge(edges[e].u, edges[e].v);
e++;
}
debug(qry[q].id, dsu.mxsz());
qry[q].ans = dsu.mxsz();
}
sort(ALL(qry), [&](auto& x, auto& y) {
return x.id < y.id;
});
for (auto q: qry)
cout << q.ans << '\n';
return 0;
}
```
:::
## Problem H. Points Separation <span class="medium pill">Medium</span>
> ###### Problem Setter: henotrix
找 $(qx_i, qy_i)$ 跟凸包最短距離,然後答案就是中垂線。
最短距離要嘛是戳在一條邊上,要嘛是戳在一個點上。
範圍很小,$\mathcal{O}(nq)$ 都可以過,你知道怎麼算點到直線距離(或是你有模板又沒有抄爛)的話應該就能 AC 了。
## Problem I. LIS Decrement <span class="medium pill">Medium</span>
> ###### Problem Setter: henotrix
經典最小割題。
:::spoiler Code
```cpp=
/// 此處省略 Dinic 模板
signed main() {
int n, ans = 0;
cin >> n;
vector<int> arr(n+2), dp(n+2), weight(n+2);
for(int i=1;i<=n;i++)
cin >> arr[i];
arr[0] = 0, arr[n+1] = n+1;
for(int i=1;i<=n;i++)
cin >> weight[i], ans += weight[i];
weight[0] = weight[n+1] = 2e9;
arr[n+1] = n+1;
for(int i=1;i<=n+1;i++)
for(int j=0;j<i;j++)
if(arr[j] < arr[i])
dp[i] = max(dp[i], dp[j]+1);
Dinic dinic;
dinic.init(2*(n+2));
for(int i=0;i<=n+1;i++)
dinic.add_edge(2*i, 2*i+1, weight[i]);
for(int i=0;i<=n+1;i++)
for(int j=0;j<i;j++)
if(dp[i] == dp[j]+1 and arr[j] < arr[i])
dinic.add_edge(2*j+1, 2*i, 2e9);
auto rrr = ans-dinic.max_flow(0, (n+1)*2+1);
cout << rrr <<'\n';
return 0;
}
```
:::
## Problem J. Randomized String Matching Algorithm <span class="medium-hard pill">Medium Hard</span>
> ###### Problem Setter: henotrix
把式子推一推就會發現你要處理每個位移的匹配位置數量,這是經典卷積題。
對每個字元分開做,把 $S$ 跟 $T$ 都當成一個只有 $0$ 跟 $1$ 的陣列,然後做 $S * \text{reverse}(T)$ 就好。
https://tioj.ck.tp.edu.tw/problems/1035
:::spoiler Code
```cpp=
/// 此處省略 NTT 模板(快速數論變換)
/// fpow(base, exp = MOD-2) 是快速冪 base^exp mod MOD
void solve() {
string S, T; cin >> S >> T;
int K; cin >> K;
int N = ssize(S), M = ssize(T);
int N_pow2 = (2 << __lg(N+1));
vector<int> match(N-M+1, 0);
for (char c = 'a'; c <= 'h'; ++c) {
vector<int> a(N_pow2, 0), b(N_pow2, 0);
for (int i = 0; i < N; ++i) a[i] = (S[i] == c);
for (int i = 0; i < M; ++i) b[i] = (T[M-i-1] == c);
NTT ntt;
ntt(a, N_pow2, false);
ntt(b, N_pow2, false);
for (int i = 0; i < N_pow2; ++i) a[i] = (a[i] * b[i]) % MOD;
ntt(a, N_pow2, true);
for (int j = M-1; j <= N-1; ++j) match[j-(M-1)] += a[j];
}
int ans = 1;
for (int i = 0; i <= N-M; ++i) {
if (match[i] == M) continue;
ans = ans * (1 - fpow(match[i], K) * fpow(fpow(M, K)) % MOD + MOD) % MOD;
}
cout << ans << "\n";
}
```
:::
## Problem K. King's Challenge <span class="hard pill">Hard</span>
> ###### Problem Setter: truckski
要算 $n! / (n-k)!$ 去掉結尾 $0$ 的末五位數。
先嘗試將答案表示為 $2^a \cdot 5^b \cdot c$ 的形式。
定義 $f_d(x)$ 代表 $d^{f_d(x)} \mid x!$ 但 $d^{f_d(x)+1} \nmid x!$。這可以在 $\mathcal{O}(\log_d x)$ 時間求出。
可得 $a = f_2(n) - f_2(n-k)$ 以及 $b = f_5(n) - f_5(n-k)$。
注意到 $\left( \prod_{\substack{1 \le x \le p-1 \\ x \bot p}} x \right)^2 \bmod p = 1$,因為每個數字可以跟他的 inverse 對上。這就代表說對任意 $x \in \mathbb{N}$,$[x, x + 2 \cdot 10^5)$ 跟 $10^5$ 互質的整數乘起來 $\!\!\bmod 100\,000$ 都是 $1$。
要計算 $c$ 就可以透過預處理 $[1, 2 \cdot 10^5]$ 前綴跟 $10^5$ 互質整數乘積來 $\mathcal{O}(1)$ 求得。
## Problem L. The Bag of Forgotten Coins <span class="easy pill">Easy</span>
> ###### Problem Setter: truckski
不能取相鄰數字的最大和,經典 DP。
用 $\text{dp}[i][0/1]$ 代表要不要取最後一個的總和最大值。
:::spoiler Code
```cpp=
void solve() {
ll N; cin >> N;
vector<ll> v(N);
for (ll &x : v) cin >> x, x = max<ll>(x, 0);
vector<ll> dp = v;
for (int i = 1; i < N; ++i) {
dp[i] = max(dp[i-1], (i >= 2 ? dp[i-2] : 0) + v[i]);
}
cout << *max_element(ALL(dp)) << "\n";
}
```
:::
## Problem M. The Tale of Professor Alya and the H-Index <span class="water pill">Water</span>
> ###### Problem Setter: truckski
```cpp
int H = 0;
for (int i = 1; i <= n; ++i) {
if (c[i] >= i) ++H;
}
```
<!-- below are css styles -->
<style>
.water {
background-color: blue;
color: white;
}
.easy {
background-color: green;
color: white;
}
.easy-medium {
background-color: PaleGoldenRod;
color: #1f2d3d;
}
.medium {
background-color: Gold;
color: white;
}
.medium-hard {
background-color: orange;
color: white;
}
.hard {
background-color: red;
color: white;
}
.pill {
display: inline-block;
border-radius: 5px;
padding: 5px 10px;
font-size: 16px;
}
.tag {
background-color: #e3e3e3;
padding: 0.5rem 0.8rem 0.5rem 1.5rem;
font-weight: bold;
border-radius: 0.5rem;
color: gray;
font-size: 14px;
}
.tag::before {
position: relative;
content: '#';
margin-left: -10px;
margin-right: 3px;
}
.warning {
color: white;
background-color: crimson;
font-weight: bold;
padding: 5px 10px;
border-radius: 5px;
}
.warning:hover {
background-color: gold;
color: black;
cursor: pointer;
}
.warning:hover::before {
position: absolute;
content: "Beware :D";
top: -150%;
left: 120px;
padding: 5px 10px;
background-color: white;
border-radius: 5px;
border: 1px solid gold;
}
</style>