# 模競 II 題解
---
<!-- .slide: data-background="https://i.imgur.com/UpmPF2T.jpg" -->
<font color="black">
<font size=7><b>羽衣光線 (Beam)</b></font><br>
<font size=6><b>Source: JAG Practice Contest for ACM-ICPC Asia Regional 2017 pA</b></font><br>
<font size=6><b>Prepared by Joylintp</b></font><br>
</font>
----
子任務 1
所有發射器皆位於所有接收器的左側
----
考慮舞台位置 $i$(0-base):
- $i \le N$,如 `>>*>)))`:答案為 $i$
- $i > N$,如 `>>>)*))`:答案為 $2N-i$
----
子任務 2
$N \le 100$
----
枚舉 $(l,r)$,$O(N)$ 檢查區間內容
時間複雜度 $O(N^3)$
----
子任務 3
$N \le 10000$
----
枚舉 $l$,在更新 $r$ 的同時檢查目前區間是否符合規則
時間複雜度 $O(N^2)$
----
子任務 4
無額外限制
----
其實就是括號匹配
然後檢查 `*` 被包在幾組 `>)` 裡面
----
輸入的字串合法
→ `*` 左邊出現多了幾個 `>`,右邊就會多幾個 `)`
→ 答案為 `*` 左邊 `>` 的數量減掉 `)` 的數量
---
<!-- .slide: data-background="https://i.imgur.com/lken7bW.jpg" -->
<font color="black">
<font size=7><b>逃離禁區 (Escape)</b></font><br>
<font size=6><b>Source: CS Academy Tree Node Distance</b></font><br>
<font size=6><b>Prepared by WiwiHo</b></font>
</font>
----
給你一棵樹
有兩個人 A, B 在不同節點上
你可以:
- 叫某人往根節點走一個點
- 叫某人回去起點
- 詢問兩人是否在同一個點
求他們的起點距離
----
其實只有起點到 LCA 的距離和 LCA 到根的距離是重要的
假設分別是 $a,b,c$
----
## Subtask 1
走到根後可以知道深度是多少
先把兩個人移到一樣的深度
然後每次都讓兩人一起往上移
並判斷是否在同一個點
----
## 滿分
一樣先找到他們的深度差
枚舉 $2^i$,讓 A 走到起點往上 $2^i$ 格
接著讓 $B$ **從他的起點**往上走 $2^{i+1}$ 格
並且每走一步(在起點也要)都檢查一次是不是在同個點
----
會相遇的時候要滿足
$2^i \geq a$
$2^{i+1} \geq b$
$a-2^i \geq b-2^{i+1}$
i.e. $2^i \geq b-a$
----
$a,b,b-a \leq D$
$\implies D \leq 2^i$
----
A 的移動次數:$2^i$
B 的移動次數:$2^1+2^2+\dots+2^{i+1}=2^{i+2}-2$
總次數 $=2^i+2(2^{i+2}-2)+2i \approx 18D$
最後找 LCA 要花 $\frac{2}{3}D$
----
```cpp=
#include "Escape.h"
#include <bits/stdc++.h>
using namespace std;
int find_distance(){
int a = 0, b = 0;
for(int i = 1; ; i *= 2){
for(int j = 0; j < i - i / 2; j++){
a += move_a();
}
for(int j = 0; j < i * 2; j++){
if(same()) goto ok;
b += move_b();
}
if(same()) goto ok;
b = 0;
reset_b();
}
ok:;
reset_a();
reset_b();
int ans = 0;
if(a > b){
ans = a - b;
for(int i = 0; i < a - b; i++) move_a();
}
else if(b > a){
ans = b - a;
for(int i = 0; i < b - a; i++) move_b();
}
while(!same()){
ans += 2;
move_a();
move_b();
}
return ans;
}
```
---
<!-- .slide: data-background="https://i.imgur.com/aTiF9cD.jpg" -->
<font color="black">
<font size=7><b>電網規劃 (Grid)</b></font><br>
<font size=6><b>Source: Codeforces 1245D</b></font><br>
<font size=6><b>Prepared by Joylintp</b></font><br>
</font>
----
子任務 1
$x_i=1, d=1$
----
所有的地區都在一直線上,且各地設置電纜難度相同
→ 若要設置電纜,只會與相鄰的城市連接
→ 將所有城市依照 $y$ 座標排序
----
設 $dp_{i,0}$ 為第 $i$ 個地區沒有電但與後面地區連接的最小花費
設 $dp_{i,1}$ 為第 $i$ 個地區有電的最小花費
----
$dp_{i,0} = dp_{i-1,0}+w_{i,i+1}$
$dp_{i,1} = \min(dp_{i-1,1}+w_{i-1,i},p_{i})$
答案為 $dp_{N,1}$
時間複雜度為 $O(N)$
(其實跟滿分解沒什麼關連)
----
子任務 2
$N \le 4$
----
$N \le 4$
→ 至多只會有 $\dfrac{N(N-1)}{2} = 6$ 條邊
→ 枚舉要蓋的電纜,並選擇每個連通塊中最便宜的地方蓋發電廠
時間複雜度 $O(2^{N^2}N^2)$
----
子任務 3
$N \le 1000, d_i \le 100$
只有一個地區 $r$ 的 $p_r=1$,其他的 $p_i=10^9$
----
蓋發電廠很貴,所以在最便宜的地區蓋就好
剩下地區用電纜連接
→ 最小生成樹 Kruskal / Prim
時間複雜度 $O(N^2\log{N})$
----
子任務 4
$N \le 1000$
----
想像有一個已經蓋好的總發電廠在地區 $0$
----
若要蓋一個新的發電廠在 $i$,
其實是設置成本為 $p_i$ 的電纜從 $0$ 到 $i$
→ 只要 $N+1$ 個地區都連通即可
→ 最小生成樹 Kruskal
時間複雜度 $O(N^2\log{N})$
----
子任務 5
無額外限制
----
在完全圖上作最小生成樹用 Prim 比較快
時間複雜度 $O(N^2)$
---
<!-- .slide: data-background="https://i.imgur.com/WUbccUW.jpg" -->
<font color="black">
<font size=7><b>笑口常開 (Laugh)</b></font><br>
<font size=6><b>Source: NTU Preliminary 2022 pI</b></font><br>
<font size=6><b>Prepared by WiwiHo</b></font><br>
</font>
----

----
## Subtask 1
$N \leq 2$
不管怎麼選都是好的
輸出 $2^N-1$
----
## Subtask 2
$N \leq 20$
位元枚舉
$O(N2^N)$
----
## Subtask 3,4
$N \leq 500$
震盪波可以以在中間的最大最小值為中心分成兩半
我們把兩半分開算(中間同時屬於左半和右半)
$dp[i][j]=$ 左半最後兩個是 $i,j$ 的數量
(所以 $i$ 和 $j$ 一個是最大值一個是最小值)
----
以下 $k < i < j$,$pos[v]= i \text{ s.t. } s_i=v$
\\[\text{if } s_i < s_j,\ dp[i][j]=\sum_{s_i<s_k<s_j} dp[k][i]\\]
\\[\text{if } s_i > s_j,\ dp[i][j]=\sum_{s_i>s_k>s_j} dp[k][i]\\]
$O(N^3)$
----
## 滿分
固定 $i$,枚舉 $j$
----
先考慮 $s_i<s_j$ 的 case
$dp[i][j]$ 的轉移來源有 $dp[k][i]$ 的條件是
$k<i$、$s_i<s_k<s_j$
如果按照 $s_j$ 遞增的順序枚舉
我們就可以用一個變數 $sum$ 記錄目前看到的 $j<i$ 的 $dp[j][i]$ 和
遇到 $i<j$ 就把 $dp[i][j]$ 加上 sum
$s_i>s_j$ 的同理
$O(N^2)$
----
```cpp=
#include <bits/stdc++.h>
#define StarBurstStream ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define iter(a) a.begin(), a.end()
using namespace std;
typedef long long ll;
const ll MOD = 1000000007;
int main(){
StarBurstStream
int n;
cin >> n;
vector<ll> s(n + 1);
for(int i = 1; i <= n; i++) cin >> s[i];
vector<vector<ll>> dp1, dp2;
auto solve = [&](vector<vector<ll>>& dp){
dp.resize(n + 1, vector<ll>(n + 1));
vector<int> pos(n + 1);
for(int i = 1; i <= n; i++) pos[s[i]] = i;
for(int i = 1; i <= n; i++){
ll sum = 1;
for(int v = s[i] + 1; v <= n; v++){
int j = pos[v];
//cerr << "test " << i << " " << j << " " << sum << "\n";
if(j < i){
sum += dp[j][i];
sum %= MOD;
}
else{
dp[i][j] += sum;
dp[i][j] %= MOD;
}
}
sum = 1;
for(int v = s[i] - 1; v >= 1; v--){
int j = pos[v];
//cerr << "test " << i << " " << j << " " << sum << "\n";
if(j < i){
sum += dp[j][i];
sum %= MOD;
}
else{
dp[i][j] += sum;
dp[i][j] %= MOD;
}
}
}
};
solve(dp1);
reverse(s.begin() + 1, s.end());
solve(dp2);
/*for(int i = 1; i <= n; i++) printv(dp1[i], cerr);
for(int i = 1; i <= n; i++) printv(dp2[i], cerr);*/
ll ans = n;
for(int i = 1; i <= n; i++){
for(int j = i + 1; j <= n; j++){
ans += dp1[i][j] * dp2[n - j + 1][n - i + 1] % MOD;
ans %= MOD;
}
}
cout << ans << "\n";
return 0;
}
```
---
<!-- .slide: data-background="https://i.imgur.com/BlWVciO.jpg" -->
<font color="black">
<font size=7><b>密碼設置 (Password)</b></font><br>
<font size=6><b>Prepared by __shaun_0124</b></font><br>
</font>
----
[詳細文字版](https://hackmd.io/@IBuVwIraTIWJeB88sQHXpw/Hk7levjRc)
----
## Subtask 1
$N,M \leq 4, C=1$
暴搜?
$2^{16}=65536$
----
## Subtask 2
$N,M \leq 20$
固定首項係數所在的位置
可以隨便填之間任的位置數量就確定了
----
$\begin{bmatrix}
1 & 0 & 0 & ? \\
0 & 1 & 0 & ? \\
0 & 0 & 1 & ?
\end{bmatrix}$
$\begin{bmatrix}
1 & 0 & ? & 0 \\
0 & 1 & ? & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}$
$\begin{bmatrix}
1 & 0 & ? & ? \\
0 & 1 & ? & ? \\
0 & 0 & 0 & 0
\end{bmatrix}$
----
假設第 $i$ 列的首項在 $a_i$
如果有 $k$ 列不是 0
那麼第 $i$ 列的問號數量是
$M-a_i-(k-i)$
----
枚舉哪些首項位置要用
$O(2^M \times M)$
----
## Subtask 3
$N=1$
枚舉首項係數要放在哪裡
----
## Subtask 4
考慮 DP
$dp[i][j]=$ 有 $i$ 列非 0 列,第一列的首項係數在**倒數**第 $j$ 行的方法數
轉移時,考慮在**上面**加一列
如果新列的首項在倒數第 $k$ 行
那麼就有 $k-i$ 個問號
$O(N^3)$
----
## Subtask 5
$dp[i][j]=(\sum_{k=i-1}^{j-1} dp[i-1][k]) \times (C+1)^{j-i}$
左邊可以前綴和
右邊可以預處理
滾動陣列
----
```cpp=
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define FOR(i, l, r, d) for(int i=(l); i<=(r); i+=(d))
const int MOD = 1000000007;
const int MAX = 5010;
int n, m, c;
int fpow[MAX];
int dp[2][MAX];
int res;
signed main(){
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m>>c;
fpow[0] = 1;
FOR(i, 1, m, 1){
fpow[i] = (fpow[i-1] * (c+1)) % MOD;
}
dp[0][0] = 1;
res = 1;
FOR(i, 1, n, 1){
int cur = i % 2;
int sum = 0;
FOR(j, i, m, 1){
sum = (sum + dp[1-cur][j-1]) % MOD;
dp[cur][j] = (sum * fpow[j-i]) % MOD;
res = (res + dp[cur][j]) % MOD;
}
}
cout<<res<<'\n';
return 0;
}
```
{"metaMigratedAt":"2023-06-17T07:31:04.020Z","metaMigratedFrom":"YAML","title":"模競 II 題解","breaks":true,"contributors":"[{\"id\":\"6b95215f-91a7-4eaf-bcfe-d43740108f96\",\"add\":2436,\"del\":33},{\"id\":\"02353542-5acb-4a66-abd0-128b1af24abb\",\"add\":5661,\"del\":28}]"}