0%

2019 臺北市資訊學科能力競賽

心得

因為11月要去比北市賽

今天解了去年北市賽的題目

(早自習30分鐘+午休30分鐘+四節下課10分鐘+無數的上課思考)

不過五題只解了四題 第五題還沒看(11/1已補)

距離北市賽 還有37天! 希望有機會進全國賽

題目:
https://docs.google.com/viewer?a=v&pid=sites&srcid=ZGVmYXVsdGRvbWFpbnxobGZnb25nenVvc2hpfGd4OjRjMTVmYmY2NzcwY2VlODM

第一題: 出戰順序 (Arrangement)

TIOJ 2169:
https://tioj.ck.tp.edu.tw/problems/2169

有練過不少題目的人應該都知道有個問題叫做「約瑟夫斯問題

這個問題有很多不同的解法

有的人可能用Treap之類的資料結構來達到 $O(n log n)$的解

不過這個問題有線性的 $O(n)$ 解

1
2
3
4
int 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
#include <bits/stdc++.h>

#define int long long
#define fastio ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);

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
3
4
2 101     2 101
5 3 => 5 3
3 1 3x 1x
2 2 2y 2y

因此我們有了一個一元模方程組

要用電腦解方程式有很多作法

用矩陣的解法

$\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
#include <bits/stdc++.h>

#define int long long
#define fastio ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);

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
#include <bits/stdc++.h>

#define fastio ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);

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
#include <bits/stdc++.h>

#define int long long
#define fastio ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);

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
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
#include <bits/stdc++.h>

#define fastio ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);

using namespace std;

const int N = 105;
int grid[N][N];
int dp[2][N][N];

signed main(){
fastio
memset(dp,-0x3f3f3f,sizeof dp);
int n,m;
cin >> n >> m;
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
cin >> grid[i][j];
dp[1][1][1] = grid[1][1];
for(int k = 1;k <= n+m-1;k++){
for(int i = 1;i <= min(k,n);i++){
for(int j = 1;j <= min(k,n);j++){
if(k==1&&i==1&&j==1) continue;
if(grid[i][k+1-i]==-1||grid[j][k+1-j]==-1){
dp[k&1][i][j] = -1e9;
continue;
}
dp[k&1][i][j] = max({dp[(k-1)&1][i][j],dp[(k-1)&1][i-1][j],dp[(k-1)&1][i][j-1],dp[(k-1)&1][i-1][j-1]});
if(i==j) dp[k&1][i][j] += grid[i][k+1-i];
else dp[k&1][i][j] += grid[i][k+1-i] + grid[j][k+1-j];
}
}
}
cout << max(0,dp[(n+m-1)&1][n][n]) << "\n";
}