心得
因為11月要去比北市賽
今天解了去年北市賽的題目 
(早自習30分鐘+午休30分鐘+四節下課10分鐘+無數的上課思考)
不過五題只解了四題 第五題還沒看(11/1已補) 
距離北市賽 還有37天! 希望有機會進全國賽
第一題: 出戰順序 (Arrangement)
TIOJ 2169:
https://tioj.ck.tp.edu.tw/problems/2169

有練過不少題目的人應該都知道有個問題叫做「約瑟夫斯問題」
這個問題有很多不同的解法 
有的人可能用Treap之類的資料結構來達到 $O(n log n)$的解 
不過這個問題有線性的 $O(n)$ 解1
2
3
4int tmp = 0;
for(int i = 2;i <= n;i++){
    tmp = (tmp+t)%i;
}
題目的要求為 給予一個最後的數字 問 $m$ 應為多少 
這題的範圍是 $1 \leq n,k \leq 10^4$ 
而 $m$ 則為 $2 \leq m \leq 3 \times 10^4$ 
因此我們可以暴搜找出 $m$ 值
複雜度: $O(30000 \times k)$
程式碼1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using namespace std;
signed main(){
	fastio
	int n,k;
	cin >> n >> k;
	for(int t = 2;t <= 30000;t++){
		int tmp = 0;
		for(int i = 2;i <= n;i++){
			tmp = (tmp+t)%i;
		}
		tmp++;
		if(tmp==k){
			cout << t << "\n";
			return 0;
		}
	}
	cout << 0 << "\n";
}
第二題:地圖編修 (Map)
TIOJ 2170:
https://tioj.ck.tp.edu.tw/problems/2170

這一題第一眼看上去,以為是向量投影 
不過仔細想一想,這題從題目的說明上看來 
其實只要解模方程 題目給了 $n$ 個方程式 
| 1 | 2 101 2 101 | 
因此我們有了一個一元模方程組
要用電腦解方程式有很多作法
用矩陣的解法
$\begin{bmatrix}3 & 2 \\ 1 & 2\end{bmatrix} \begin{bmatrix}x \\ y \end{bmatrix} = \begin{bmatrix}5 \\ 3 \end{bmatrix}$
或者克拉瑪公式
以及這題需要使用的高斯消去法
高斯消去法是基於加減消去法 運用矩陣的列運算來求解
$\begin{bmatrix}3(x) & 2(y) & 5 \\ 1(x) & 2(y) & 3 \end{bmatrix}$
只要我們能夠用列運算將原本的矩陣化為
$\begin{bmatrix}1(x) & 0(y) & x \\ 0(x) & 1(y) & y \end{bmatrix}$
最後一行就會是所有我們要求解的變數了
而使用高斯消去法解模方程就只是在運算時去執行模運算而已 
至於如何進行列運算來轉化矩陣 就是留給讀者的練習了
複雜度: $O(n^3logm)$ (備註:$logm$是因為求模逆元)
程式碼1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
using namespace std;
const int N = 105;
int MOD;
int fastpow(int n, int p){
	int res = 1;
	while(p){
		if(p&1) res = (res * n)%MOD;
		n = (n*n)%MOD;
		p>>=1;
	}
	return res;
}
int arr[N][N];
signed main(){
	fastio
	int n;
	cin >> n >> MOD;
	for(int i = 1;i <= n+1;i++){
		for(int j = 1;j <= n;j++){
			cin >> arr[j][n-i+2]; 
		}
	}
	for(int i = 1;i <= n;i++){
		if(!arr[i][i]){
			//若arr[i][i]為0 與其他行交換 將arr[i][i]變為其他數字
			bool ok = true;
			int row = 0;
			for(int j = i+1;j <= n+1;j++){
				if(arr[j][i]){
					ok = false;
					row = j;
					break;
				}
			}
			if(ok) continue;
			for(int j = 1;j <= n+1;j++){
				swap(arr[i][j],arr[row][j]);
			}
		}
		int tmp = arr[i][i];
        //將這一行同除arr[i][i] 使arr[i][i]變為1
		for(int j = i;j <= n+1;j++){
			arr[i][j] = arr[i][j] * fastpow(tmp,MOD-2)%MOD;
		}
		for(int j = 1;j <= n;j++){
			if(i==j) continue;
			tmp = arr[j][i];
			for(int k = 1;k <= n+1;k++){
				arr[j][k] = ((arr[j][k]%MOD - arr[i][k]%MOD*tmp%MOD)%MOD+MOD)%MOD;
			}
		}
	}
	for(int i = n;i;i--) cout << (arr[i][n+1]+MOD)%MOD << " ";
}
第三題:打卡遊戲 (Checkin)
TIOJ 2171:
https://tioj.ck.tp.edu.tw/problems/2171

圖論題
這題目其實我看了一陣子才理解
題目給的是一張有$A+B$ 個節點與 $k$ 條邊的圖(不一定完全連通)
而 $S,M$ 的數值對於這題來說其實完全用不到
要用最少車資完成遊戲 我們可以推得 
距離和不論怎麼做都不會改變 
因此我們可以將題目的「最少車資」改為「搭乘最少數量」
而我們要找尋搭乘的最少數量 
我們可以很輕易的知道 如果有 $n$ 個連通塊 
我們至少要做 $n$ 次才能走完所有邊 
而每個連通塊需要的次數 我們可以由「一筆畫定理」得知

因此本題的答案就是
複雜度 $O(A+B+K)$ 
程式碼1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using namespace std;
const int N = 1005;
vector<int> adj[N];
int ans = 0, visited[N];
int odd = 0;
void dfs(int u){
	visited[u] = 1;
	if(adj[u].size()&1) odd++;
	for(auto v : adj[u]){
		if(!visited[v]) dfs(v);
	}
}
signed main(){
	fastio
	int a,b,s,m,k;
	cin >> a >> b >> s >> m >> k;
	for(int i = 0;i < k;i++){
		int u,v,w;
		cin >> u >> v >> w;
		//v starts from a+1
		adj[u].push_back(a+v);
		adj[a+v].push_back(u);
	}
	for(int i = 1;i <= a+b;i++){
		if(visited[i]||adj[i].size()==0) continue;
		odd = 0;
		dfs(i);
		ans += max(odd/2,1);
	}
	cout << ans << "\n";
}
第四題:物種演化 (Evolution)
TIOJ 2172:
https://tioj.ck.tp.edu.tw/problems/2172

這題是非常裸的樹上兩點最短距離
就用倍增法或樹壓平找LCA
之後用 $d = dep[u] - 2*dep[lca(u,v)] + dep[v]$就是答案
程式碼1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
using namespace std;
const int N = 1e5+5;
vector<int> adj[N];
int dep[N], fa[N][25];
void dfs(int u, int par){
	for(int v : adj[u]){
		if(v==par) continue;
		dep[v] = dep[u]+1;
 		fa[v][0] = u;
		dfs(v,u);
	}
}
int lca(int x, int y){
	if(dep[x] < dep[y]) swap(x,y);
	int diff = dep[x]-dep[y];
	for(int i = 20;~i;i--){
		if((1<<i)&diff){
			x = fa[x][i];
		}
	}
	if(x==y){
		return x;
	}
	for(int i = 20;~i;i--){
		if(fa[x][i]!=fa[y][i]){
			x = fa[x][i];
			y = fa[y][i];
		}
	}
	x = fa[x][0];
	return x;
}
signed main(){
	fastio
	int n,m;
	cin >> n >> m;
	for(int i = 1;i < n;i++){
		int u,v;
		cin >> u >> v;
		u++,v++;
		adj[u].push_back(v);
		adj[v].push_back(u);
	}
	dep[1] = 0;
	dfs(1,1);
	for(int i = 1;i <= 20;i++){
		for(int j = 1;j <= n;j++){
			fa[j][i] = fa[fa[j][i-1]][i-1];
		}
	}
	while(m--){
		int x,y;
		cin >> x >> y;
		x++,y++;
		cout << dep[x]+dep[y]-2*dep[lca(x,y)] << "\n";
	}
}
第五題:搜集寶藏(Treasure)
TIOJ 2173:
https://tioj.ck.tp.edu.tw/problems/2173
(11/1 補)
這題是一個兩條路徑的DP問題,因此,我們要思考如何去紀錄兩條路線拿到寶藏的最大值,我們可以設 $dp[t][i][j]$ 為在時間 $t$ 的時候,第一個人的縱座標在 $i$,第二個人的縱座標在 $j$,然後我們就可以從前一個時間轉移到現在這個時間(這個條件使得我們可以用滾動DP來加速,是說沒有那個必要就是了)
| 1 | 
 |