---
# System prepended metadata

title: 2024-2025 CTU Open Contest

---

2024-2025 CTU Open Contest
===
比賽鏈結
---
https://codeforces.com/gym/105442/

比賽影片
---
{%youtube FhDS6EqFr3c%}

記分板
---
![image](https://hackmd.io/_uploads/rJxR9kxbyx.png)

題解
---
### A. Flag Bearer
---
#### 題目摘要
將旗語轉成英文字母後再位移，最後再轉成旗語

#### 想法
實作題，加油!!!

:::spoiler 實作
```cpp=
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define F first
#define S second

int moves[8][2]={{1, 0}, {1, -1}, {0, -1}, {-1, -1}, {-1, 0}, {-1, 1}, {0, 1}, {1, 1}};

int tot=0;
int read(){
    char c[10][10];
    for(int i=0;i<9;i++) for(int j=0;j<9;j++) cin >> c[i][j];
    pii dir={-1, -1};
    for(int i=0;i<8;i++){
        if (c[moves[i][0]+4][moves[i][1]+4]=='#'){
            if (dir.F==-1) dir.F=i;
            else dir.S=i;
        }
    }
    if (dir.F==4&&dir.S==6) return 'j'-'a';
    if (dir.F==4&&dir.S==7) return 'v'-'a';
    if (dir.F==3&&dir.S==6) return 'y'-'a';
    if (dir.F==6&&dir.S==7) return 'z'-'a';
    if (dir.F==0) return dir.S-1;
    if (dir.F==1&&dir.S-dir.F<=2) return 'h'-'a'+(dir.S-1)-1;
    if (dir.F==1) return 'k'-'a'+(dir.S-3)-1;
    if (dir.F==2) return 'o'-'a'+(dir.S-dir.F)-1;
    if (dir.F==3) return 't'-'a'+(dir.S-dir.F)-1;
    if (dir.F==5) return 'w'-'a'+(dir.S-dir.F)-1;
}

pii convert(int x){
    pii dir;
    if (x==9) return {4, 6};
    if (x==21) return {4, 7};
    if (x==24) return {3, 6};
    if (x==25) return {6, 7};
    if (0<=x&&x<=6) return {0, x+1};
    if (7<=x&&x<=8) return {1, x-5};
    if (10<=x&&x<=13) return {1, x-6};
    if (14<=x&&x<=18) return {2, x-11};
    if (19<=x&&x<=20) return {3, x-15};
    if (22<=x&&x<=23) return {5, x-16};
}

void output(pii x){
    char c[10][10];
    for(int i=0;i<9;i++) for(int j=0;j<9;j++) c[i][j]='.';
    c[4][4]='*';
    for(int i=0;i<8;i++){
        if (x.F==i||x.S==i){
            for(int k=1;k<4;k++){
                c[4+moves[i][0]*k][4+moves[i][1]*k]='#';
            }
        }
    }
    for(int i=0;i<9;i++){
        for(int j=0;j<9;j++) cout << c[i][j];
        cout << '\n';
    }
}

void solve(){
    int n, m;
    cin >> n >> m;
    for(int i=0;i<n;i++){
        int x=read();
        x+=m;
        x%=26;
        output(convert(x));
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int _t=1;
    // cin >> _t;
    while(_t--) solve();
}
```
:::

### B. Cowpproximation
---
#### 題目摘要

#### 想法

:::spoiler 實作
```cpp=
```
:::

### C. Reptile Eggs
---
#### 題目摘要

#### 想法

:::spoiler 實作
```cpp=
```
:::

### D. Fishception
---
#### 題目摘要
給你很多點，這些點原本都是各自長方形的四個角，保證任何一個長方形不會相交，並且比較大的長方形會包含所有比較小的長方形，並且除了最小的長方形，每個長方形會包住一個長方形，問你最小的長方形的面積。
#### 想法
發現根據限制最左、右、上、下的點一定是最外層那個長方形的點，對每個點的x和y sort過後，每次拔掉最外層的點，剩下四個點就是答案要的四個點了。
:::spoiler 實作
```cpp=
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define F first
#define S second
#define rep(a, b, c) for(int a = b; a < c; a++)
#define pb push_back
#define all(x) (x).begin(), (x).end()

const int IINF = 1e9 + 7;

void solve(){
    int n; cin >> n;
    vector<tuple<ll, ll, int>> X(n), Y(n);
    rep (i, 0, n) {
        auto &[x, y, id] = X[i];
        cin >> x >> y;
        id = i;
        auto &[y2, x2, idd] = Y[i];
        y2 = y, x2 = x, idd = id;
    }
    sort(all(X));
    sort(all(Y));
    deque<tuple<ll, ll, int>> XX, YY;
    for (auto a : X) XX.pb(a);
    for (auto a : Y) YY.pb(a);
    set<int> st;
    while (n - st.size() > 4) {
        while (st.count(get<2>(XX.front()))) XX.pop_front();
        while (st.count(get<2>(XX.back()))) XX.pop_back();
        // while (st.count(get<2>(YY.front()))) YY.pop_front();
        // while (st.count(get<2>(YY.back()))) YY.pop_back();
        st.insert(get<2>(XX.front()));
        // cout << get<0>(XX.front()) << ' ' << get<1>(XX.front()) << '\n';
        XX.pop_front();
        st.insert(get<2>(XX.back()));
        // cout << get<0>(XX.back()) << ' ' << get<1>(XX.back()) << '\n';
        XX.pop_back();
        // while (st.count(get<2>(XX.front()))) XX.pop_front();
        // while (st.count(get<2>(XX.back()))) XX.pop_back();
        while (st.count(get<2>(YY.front()))) YY.pop_front();
        while (st.count(get<2>(YY.back()))) YY.pop_back();
        st.insert(get<2>(YY.front()));
        // cout << get<1>(YY.front()) << ' ' << get<0>(YY.front()) << '\n';
        YY.pop_front();
        st.insert(get<2>(YY.back()));
        // cout << get<1>(YY.back()) << ' ' << get<0>(YY.back()) << '\n';
        YY.pop_back();
        assert(st.size() % 4 == 0);
    }
    // cerr << XX.size() << ' ' << YY.size() << '\n';
    while (st.count(get<2>(XX.front()))) XX.pop_front();
    while (st.count(get<2>(XX.back()))) XX.pop_back();
    while (st.count(get<2>(YY.front()))) YY.pop_front();
    while (st.count(get<2>(YY.back()))) YY.pop_back();
    // for (auto [x, y, i] : XX) cout << x << ' ' << y << ' ' << i << '\n';
    // for (auto [y, x, i] : YY) cout << x << ' ' << y << ' ' << i << '\n';
    st.insert(get<2>(YY.front()));
    st.insert(get<2>(YY.back()));
    while (st.count(get<2>(XX.front()))) XX.pop_front();
    while (st.count(get<2>(XX.back()))) XX.pop_back();
    auto [x, y, i] = XX[0];
    auto [y2, x2, ii] = YY.back();
    auto [y3, x3, iii] = YY[0];
    // cout << x << ' ' << y << ' ' << x2 << ' ' << y2 << ' ' << x3 << ' ' << y3 << '\n';
    cout << abs((x2 - x) * (y3 - y) - (x3 - x) * (y2 - y)) << '\n';
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int _t=1;
    // cin >> _t;
    while(_t--) solve();
}
```
:::

### E. Pigpartite Giraffe
---
#### 題目摘要

#### 想法

:::spoiler 實作
```cpp=
```
:::

### F. Hamster
---
#### 題目摘要
給$N \times M$的圖，點權$a_{ij}$，求從左上走到右下不經過走過的點的最大權重和。
#### 想法

發現N, M其中一個是奇數一定能全拿。

若是偶數的話，可以觀察到最好狀況一定只會不取一個點，而可以選的點的$i + j$要是奇數，找到最小值減掉就是答案了。

:::spoiler 實作
```cpp=
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define F first
#define S second
#define rep(a, b, c) for(int a = b; a < c; a++)

const int IINF = 1e9 + 7;

void solve(){
    int n, m; cin >> n >> m;
    vector g(n, vector<int>(m));
    ll ans = 0;
    rep (i, 0, n) rep (j, 0, m) {
        cin >> g[i][j];
        ans += g[i][j];
    }
    if ((n & 1) || (m & 1)) {
        cout << ans << '\n';
    } else {
        int mi = IINF;
        rep (i, 0, n) rep (j, 0, m) if ((i + j) & 1) mi = min(mi, g[i][j]);
        cout << ans - mi << '\n';
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int _t=1;
    // cin >> _t;
    while(_t--) solve();
}
```
:::

### G. Pray Mink
---
#### 題目摘要
給正整數$N$，每次可以刪掉其中一個位數，若出現前導0就把前導0全刪，求在第一次出現合數前能出現多少次質數。
#### 想法
位數只有10，開心dfs，刪掉一個位數就把深度加一，出現合數就剪枝，出現前導0就不要加深度，答案就是能走到的最大深度

判質數用根號，讚


:::spoiler 實作
```cpp=
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define F first
#define S second
#define rep(a, b, c) for(int a = b; a < c; a++)
#define pb push_back

const int IINF = 1e9 + 7;

void solve(){
    string s; cin >> s;
    int n = s.size();
    auto check = [&](int p) -> bool {
        if (p == 1) return false;
        for (int i = 2; i * i <= p; i++) {
            if (p % i == 0) return false;
        }
        return true;
    };
    if (check(stoi(s)) == 0) {
        cout << 0 << '\n';
        return;
    }
    int ans = 0;
    auto dfs = [&](auto self, int mask, int dep) -> void {
        ans = max(ans, dep);
        if (__builtin_popcount(mask) == 1) return;
        rep (i, 0, n) if (mask >> i & 1) {
            string tmp = "";
            rep (j, 0, n) if (i != j && (mask >> j & 1)) tmp.pb(s[j]);
            if (tmp[0] == '0') {
                self(self, mask ^ (1 << i), dep);
            } else if (check(stoi(tmp))) {
                self(self, mask ^ (1 << i), dep + 1);
            }
        }
    };
    dfs(dfs, (1 << n) - 1, 1);
    cout << ans << '\n';
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int _t=1;
    // cin >> _t;
    while(_t--) solve();
}
```
:::

### H. Ornithology
---
#### 題目摘要
兩個水管，被分成$n$個位置。一開始所有鳥都在第一個水管上，並且第$i$個位置有$p_i$隻鳥，每隻鳥會有到第二根水管的目標位置，求所有鳥從第一根水管到第二根的路徑有幾個交點(不會有鳥飛行路徑一模一樣)

#### 想法
裸逆序數對

:::spoiler 實作
```cpp=
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define F first
#define S second
#define rep(a, b, c) for(int a = b; a < c; a++)

const int IINF = 1e9 + 7;

const int N=2e5+10;
ll bit[N];

int n;
void update(int x, ll k){for(;x<=n;x+=x&-x) bit[x]+=k;}
ll query(int x){
    ll res=0;
    for(;x;x-=x&-x) res+=bit[x];
    return res;
}

void solve(){
    cin >> n;
    vector<int> v;
    for(int i=0;i<n;i++){
        int p;
        cin >> p;
        vector<int> tmp(p);
        for(auto &x:tmp) cin >> x;
        sort(tmp.begin(), tmp.end());
        for(auto x:tmp) v.push_back(x+1);
    }
    ll ans=0;
    for(auto x:v){
        ans+=query(n)-query(x);
        update(x, 1);
    }
    cout << ans << '\n';
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int _t=1;
    // cin >> _t;
    while(_t--) solve();
}
```
:::

### I. P||k Cutting
---
#### 題目摘要
求有多少區間元素or起來是$K$

#### 想法
對位元做事，找以位置作為左端的右端合法區間，最後再求一個位置的所有位元的右端合法區間的交集，拿區間長度更新答案就好

:::spoiler 實作
```cpp=
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define F first
#define S second
#define rep(a, b, c) for(int a = b; a < c; a++)

const int IINF = 1e9 + 7;


void solve(){
    int n, k;
    cin >> n >> k;
    vector<int> a(n+1);
    vector<vector<int>> L(n+1, vector<int>(31, 0)), R(n+1, vector<int>(31, 0));
    for(int i=1;i<=n;i++) cin >> a[i];
    for(int j=0;j<=30;j++){
        vector<int> val(n+1, 0);
        for(int i=1;i<=n;i++) val[i]=(a[i]>>j&1);
        int z=1;
        for(int i=1;i<=n;i++){
            if (z<i) z=i;
            while(z<=n&&val[z]==0) z++;
            if (k>>j&1){
                L[i][j]=z;
                R[i][j]=n+1;
            }else{
                L[i][j]=i;
                R[i][j]=z;
            }
        }
    }
    ll ans=0;
    for(int i=1;i<=n;i++){
        int l=L[i][0], r=R[i][0];
        for(int j=0;j<=30;j++){
            l=max(l, L[i][j]);
            r=min(r, R[i][j]);
        }
        ans+=max(0, r-l);
    }
    cout << ans << '\n';
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int _t=1;
    // cin >> _t;
    while(_t--) solve();
}
```
:::

### J. Rabid Rabbit
---
#### 題目摘要
一個長度為$N$的陣列及$Q$筆詢問，問$[L, R]$內有多少個不同的費氏數$F=a_i + a_j \ (L \leq i \leq j \leq R)$
#### 想法
觀察到$a_i$最大只有到$10^9$，根據我爆搜出來最多只需要維護45個費氏數，因此可以有個簡單的作法。

考慮詢問離線，讓左界從大到小，對每個費氏數維護他的一個左界以右的 $(\ell, \ r)$，代表$\,a_{\ell} + a_r = F$，可以取到這個費氏數的區間就是當詢問的右界$\ge r$。因此會很直覺地想要讓他的r越小越好，所以要做的就是在左界往左時好好維護每個費氏數的(l, r)，然後開一顆BIT維護答案就好。

複雜度$O(N \times 45 + Q \log N)$
:::spoiler 實作
```cpp=
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define F first
#define S second
#define rep(a, b, c) for(int a = b; a < c; a++)
#define pb push_back
#define all(x) (x).begin(), (x).end()
#define pii pair<int, int>
#define F first
#define S second


const int MAXN = 1e5 + 5;
const int IINF = 1e9 + 7;

int BIT[MAXN];

void mod(int x, int val) {
    while (x <= MAXN - 5) {
        BIT[x] += val;
        x += x & -x;
    }
}

int query(int x) {
    int res = 0;
    while (x) {
        res += BIT[x];
        x -= x & -x;
    }
    return res;
}

void solve(){
    int n, q; cin >> n >> q;
    vector<ll> a(n + 1), f;
    rep (i, 1, n + 1) cin >> a[i];
    f.pb(1);
    f.pb(2);
    while (f.end()[-2] + f.back() <= 2000000000) {
        f.pb(f.end()[-2] + f.back());
    }
    const int SZ = 45;
    vector<vector<pii>> que(n + 1);
    rep (i, 0, q) {
        int l, r; cin >> l >> r;
        l++, r++;
        que[l].pb({r, i});
    }
    vector<int> fid(SZ, n + 1);
    map<ll, int> mp;
    vector<int> ans(q, 0);
    for (int l = n; l > 0; --l) {
        rep (i, 0, SZ) {
            if (mp.find(f[i] - a[l]) == mp.end()) continue;
            if (mp[f[i] - a[l]] > fid[i]) continue; 
            if (fid[i] != n + 1) {
                mod(fid[i], -1);
            }
            fid[i] = mp[f[i] - a[l]];
            mod(fid[i], 1);
        }
        for (auto [r, id] : que[l]) {
            ans[id] = query(r);
        }
        mp[a[l]] = l;
    }
    rep (i, 0, q) cout << ans[i] << '\n';
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int _t=1;
    // cin >> _t;
    while(_t--) solve();
}
```
:::

### K. Fellow Sheep
---
#### 題目摘要
給你由很多個單位組成的一張圖，然後要你求最大流。

#### 想法
每個單位只有五條邊，然後每個單位的出點接在下一個單位的入點上，所以對每個單位個別算好以後取min就好。(好像其實是最小割但反正一樣)
:::spoiler 實作
```cpp=
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define F first
#define S second
#define rep(a, b, c) for(int a = b; a < c; a++)

const int IINF = 1e9 + 7;

void solve(){
    int n; cin >> n;
    int ans = IINF;
    rep (i, 0, n) {
        vector<int> a(5);
        rep (j, 0, 5) cin >> a[j];
        int g = min(a[0], a[1]);
        a[0] -= g;
        a[1] -= g;
        int sum = g;
        if (a[0]) a[3] += min(a[2], a[0]);
        else {
            a[0] += min(a[2], a[1]);
            sum += min(a[2], a[1]);
            a[3] -= min(a[2], a[1]);
        }
        sum += min(a[3], a[4]);
        ans = min(ans, sum);
    }
    cout << ans << '\n';
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int _t=1;
    // cin >> _t;
    while(_t--) solve();
}
```
:::

### L. Watchdogs
---
#### 題目摘要

#### 想法
用lca來算路徑長，將奇數長度路徑中間點先點好，如果是偶數，若其中一個已經有被點過的就跳過，否則將中間兩個點的邊存起來，最小值就會是這些邊重新建圖的最小點覆蓋。

觀察到樹上一條邊的兩個點深度可以分奇偶，因此可以用深度的奇偶性建張二分圖的流模型，跑個最大流加上原本點過的點就是答案。

複雜度應該是$O(N \sqrt N)$
:::spoiler 實作
```cpp=
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define F first
#define S second
#define rep(a, b, c) for(int a = b; a < c; a++)
#define pb push_back
#define all(x) (x).begin(), (x).end()
#define pii pair<int, int>
#define F first
#define S second

const int MAXN = 1e5 + 5;
const int IINF = 1e9 + 7;

struct Dinic {
    struct edge {
        int v, r; ll rc;
    };
    vector<vector<edge>> adj;
    vector<int> dis, it;
    Dinic(int n) : adj(n), dis(n), it(n) {}
    void add_edge(int u, int v, ll c) {
        adj[u].pb({v, adj[v].size(), c});
        adj[v].pb({u, adj[u].size() - 1, 0});
    }
    bool bfs(int s, int t) {
        fill(all(dis), IINF);
        queue<int> q;
        q.push(s);
        dis[s] = 0;
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            for (const auto &[v, r, rc] : adj[u]) {
                if (dis[v] < IINF || rc == 0) continue;
                dis[v] = dis[u] + 1;
                q.push(v);
            }
        }
        return dis[t] < IINF;
    }
    ll dfs(int u, int t, ll cap) {
        if (u == t || cap == 0) return cap;
        for (int &i = it[u]; i < (int)adj[u].size(); ++i) {
            auto &[v, r, rc] = adj[u][i];
            if (dis[v] != dis[u] + 1) continue;
            ll tmp = dfs(v, t, min(cap, rc));
            if (tmp > 0) {
                rc -= tmp;
                adj[v][r].rc += tmp;
                return tmp;
            }
        }
        return 0;
    }
    ll flow(int s, int t) {
        ll ans = 0, tmp;
        while (bfs(s, t)) {
            fill(all(it), 0);
            while ((tmp = dfs(s, t, IINF)) > 0) {
                ans += tmp;
            }
        }
        return ans;
    }
};

void solve(){
    int n, m; cin >> n >> m;
    vector<vector<int>> adj(n);
    rep (i, 0, n - 1) {
        int a, b; cin >> a >> b;
        adj[a].pb(b);
        adj[b].pb(a);
    }
    vector<int> dep(n);
    vector<vector<int>> pa(n, vector<int>(20));
    auto dfs = [&](auto self, int u, int p) -> void {
        for (int v : adj[u]) {
            if (v == p) continue;
            dep[v] = dep[u] + 1;
            pa[v][0] = u;
            self(self, v, u);
        }
    };
    dep[0] = 0;
    dfs(dfs, 0, -1);
    rep (j, 1, 20) rep (i, 0, n) pa[i][j] = pa[pa[i][j - 1]][j - 1];
    vector<int> ok(n, 0);
    vector<pii> mou;
    auto lca = [&](int a, int b) -> int {
        if (dep[a] < dep[b]) swap(a, b);
        int d = dep[a] - dep[b];
        rep (i, 0, 20) if (d >> i & 1) a = pa[a][i];
        if (a == b) return a;
        for (int i = 19; i >= 0; --i) {
            if (pa[a][i] != pa[b][i]) {
                a = pa[a][i];
                b = pa[b][i];
            }
        }
        return pa[a][0];
    };
    rep (i, 0, m) {
        int a, b; cin >> a >> b;
        int p = lca(a, b);
        int dis = dep[a] + dep[b] - dep[p] * 2;
        if (dep[a] < dep[b]) swap(a, b);
        if (dis & 1 ^ 1) {
            dis /= 2;
            rep (j, 0, 20) if (dis >> j & 1) {
                a = pa[a][j];
            }
            ok[a] = 1;
        } else {
            mou.pb({a, b});
        }
    }
    Dinic G(n + 2);
    int s = n, t = s + 1;
    rep (i, 0, n) {
        if (dep[i] & 1) G.add_edge(i, t, 1);
        else G.add_edge(s, i, 1);
    }
    for (auto [a, b] : mou) {
        int p = lca(a, b);
        int dis = dep[a] + dep[b] - dep[p] * 2;
        // cout << dis << ' ' << a << ' ' << b << ' ';
        dis /= 2;
        rep (i, 0, 20) if (dis >> i & 1) a = pa[a][i];
        // cout << a << '\n';
        if (ok[a] || ok[pa[a][0]]) continue;
        if (dep[a] & 1) G.add_edge(pa[a][0], a, 1);
        else G.add_edge(a, pa[a][0], 1);
    }
    int ans = 0;
    rep (i, 0, n) {
        // cout << ok[i] << ' ';
        ans += ok[i];
    }
    cout << ans + G.flow(s, t) << '\n';
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int _t=1;
    // cin >> _t;
    while(_t--) solve();
}
```
:::