最後更新日期 2024 / 01 / 01

Codeforces 918 Div 4 題解

前言

為了慶祝第一次在 CF 破台所以難得來寫一下題解
這成績我可以開心一個禮拜了 :>
然後今天早上過的太廢一題都沒寫不知道是不是因為這樣把能量都留到這場釋放了

pA - Odd One Out

題意

給定三個數,其中兩個數相同,找到不同的那一個數

思路

直接照題意模擬做就行了,實作的部分用 map 很方便

程式碼

#include <bits/stdc++.h>
using namespace std;
 
int main() {
    int t, tmp;
    cin >> t;
    while (t--) {
        map<int, int> cnt;
        // 計算每個數字出現幾次
        for (int i = 1; i <= 3; i++) {
            cin >> tmp;
            cnt[tmp]++;
        }
        // 找到出現次數為 1 的那個數
        for (auto [num, frq] : cnt) {
            if (frq == 1) cout << num << endl;
        }
    }
}

pB - Not Quite Latin Square

題意

給予一個 3x3 的矩陣,每行、列都剛好包含各一個 A、B、C 字母,但某一格未知,任務是找到未知的那格是什麼

思路

直接照題意模擬做就行了,實作的部分用 set 很方便

程式碼

#include <bits/stdc++.h>
using namespace std;

char tb[5][5];

// 檢查矩陣是否合法
bool check() {
    // 檢查每個 row
    for (int i = 0; i < 3; i++) {
        set<char> st;
        for (int j = 0; j < 3; j++) {
            st.insert(tb[i][j]);
        }
        if (st.size() != 3) return false;
    }
    // 檢查每個 column
    for (int i = 0; i < 3; i++) {
        set<char> st;
        for (int j = 0; j < 3; j++) {
            st.insert(tb[j][i]);
        }
        if (st.size() != 3) return false;
    }
    return true;
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        int px, py; // 未知格的座標
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                cin >> tb[i][j];
                if (tb[i][j] == '?') {
                    px = i;
                    py = j;
                }
            }
        }
        // 枚舉該放哪個字母
        for (char c = 'A'; c <= 'C'; c++) {
            tb[px][py] = c;
            // 如果合法就輸出
            if (check()) {
                cout << c << endl;
            }
        }
    }
}

pC - Can I Square?

題意

n 個盒子,第 i 個盒子裝著 ai 個 1x1 的紙片,求是否可以剛好用所有的紙片拼成一個平片的正方形?

思路

事實上題目問的就只是 i=1nai 是否為完全平方數
所以只需要加總後開根在平方看是否跟原始的數相等就行了

程式碼

#include <bits/stdc++.h>
#define int long long
#define square(x) ((x)*(x))
using namespace std;

signed main() {
    int t;
    cin >> t;
    while (t--) {
        int n, tmp, sum = 0;
        cin >> n;
        for (int i = 0; i < n; i++) {
            cin >> tmp;
            sum += tmp;
        }
        if (square((int)sqrt(sum)) == sum) {
            cout << "YES" << endl;
        } else {
            cout << "NO" << endl;
        }
    }
}

pD - Unnatural Language Processing

題意

有一種只包含 a、b、c、d、e 的語言,這些字母被分成兩種類型

  • Vowels — 包含字母 a 和 e,用 V 表示
  • Consonants — 包含字母 b、c、d,用 C 表示

一個合法的段落可能為 CV 或 CVC
給予一串文字
求將它按照規則分段的結果

思路

直接隨便取的話會出一些問題
不過其實只需要觀察到兩個 C 不能連再一起就完事了
雖然我也還沒完整的證過 (因為我不是數學家)

程式碼

#include <bits/stdc++.h>
using namespace std;

// 儲存每個字母是 v 或 c 類
map<char, char> tb = {
    {'a', 'v'},
    {'b', 'c'},
    {'c', 'c'},
    {'d', 'c'},
    {'e', 'v'},
};

void sol() {
    int n;
    string s; // 題目給的字串
    string ss; // 轉換過後只包含 c 跟 v 的字串
    cin >> n >> s;
    // 轉換字串
    for (auto c : s) ss += tb[c];
    // 分段後的結果
    vector<string> ans;
    for (int i = 0; i < s.size(); ) {
        if (i == s.size() - 3) { // 只剩下最後三個字就直接全取
            ans.push_back(s.substr(i, 3));
            break;
        }
        if (ss[i+3] == ss[i+2] && ss[i+2] == 'c') {
            // 如果選 CV 會導致兩個連續 C 的情況出現就改選 CVC
            ans.push_back(s.substr(i, 3));
            i = i + 3;
        } else {
            // 否則就選 CV
            ans.push_back(s.substr(i, 2));
            i = i + 2;
        }
    }    
    // 輸出解
    for (int i = 0; i < ans.size(); i++) {
        if (i != 0) cout << ".";
        cout << ans[i];
    }
    cout << endl;
}
 
int main() {
    int t;
    cin >> t;
    while (t--) sol();
}

pE - Romantic Glasses

題意

給定一個陣列,求是否存在一段連續的區間滿足偶數 index 的所有元素加起來等於奇數 index 的元素

思路

把偶數 index 的元素都加上負號再套上前綴和之後
題目就被轉換成找到一個區間滿足總和為 0

也就是說只要從左掃到右
並對於每個位置檢查左邊是否存在相同值的元素即可

程式碼

#include <bits/stdc++.h>
#define int long long
using namespace std;
 
void sol() {
    int n;
    cin >> n;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
        if (i % 2 == 0) a[i] *= -1;
    }
    vector<int> psum(n + 1);
    partial_sum(a.begin(), a.end(), psum.begin() + 1);
    set<int> st;
    for (auto e : psum) {
        if (st.find(e) != st.end()) {
            cout << "YES" << endl;
            return;
        }
        st.insert(e);
    }
    cout << "NO" << endl;
}
 
signed main() {
    int t;
    cin >> t;
    while (t--) sol();
}

pF - Greetings

題意

n 個人在數線上以每秒 1 格的速度往右走,第 i 個人的起點為 ai 終點為 bi,求他們倆倆碰面(同時出現在同一格)的次數

思路

仔細觀察之後會發現
只有 ai > ajbi < bj 的情況下 ij 才會碰面
(也就是一個區間把另外一個區間包住的情況)

可以發現其實就跟 CSES 的 Nested Ranges Count 完全一樣
那這題網路上題解很多
算法細節就不多提了

程式碼

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#define int long long

using namespace std;
using namespace __gnu_pbds;

template <class T> using ordered_set = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;

void sol() {
    int n;
    cin >> n;
    vector<int> becon(n), yidx(n);
    vector<tuple<int, int, int>> b(n), c(n);
 
    for (int i = 0; i < n; ++i) {
		int l, r;
		cin >> l >> r;
        b[i] = {l, r * -1, i};
        c[i] = {r, l * -1, i};
    }
 
    sort(b.begin(), b.end());
    sort(c.begin(), c.end());
    
	for (int i = 0; i < n; ++i)
        yidx[get<2>(b[i])] = i;
 
    ordered_set<int> s;
    
    for (int i = 0; i < n; ++i) {
        int idx = get<2>(c[i]);
        int cur = yidx[idx];
        becon[idx] = s.size() - s.order_of_key(cur);
        s.insert(cur);
    }
 
    int ans = 0;
    for (int i = 0; i < n; ++i) ans += becon[i];
    cout << ans << endl;
}
 
signed main() {
    io;
    int t;
    cin >> t;
    while (t--) sol();
}

pG - Bicycles

題意

給一張無向帶權圖,第 i 條邊連接著 viui 且權重為 wi,你可以在節點 i 拿到一台速度為 si 的腳踏車,騎著速度為 si 的腳踏車經過第 j 條邊時需要花費 si×wj,求全程騎腳踏車從 1 走到 n 的最少時間花費

思路

不難想到從點 v 獲得腳踏車後到其他每個點 u 的最少時間花費為 dist(v,u)sv (其中 dist 代表兩點最短路徑長度)
那其實就只要依照上面的規則把那些邊都建在新的圖上
最後對新的圖做最短路就可以了

補充資料: 最短路演算法

#include <bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define ff first
#define ss second

using namespace std;

const int maxn = 1e3 + 10;
const int inf = 1e13;
 
int n, m;
vector<pii> g[maxn];
int bike[maxn];
 
vector<pii> g2[maxn];
int dist2[maxn][maxn];
 
int dist[maxn];
 
void sol() {
    // 初始化 & 讀取輸入
    cin >> n >> m;
    for (int i = 0; i <= n; i++) {
        for (int j = 0; j <= n; j++) {
            dist2[i][j] = inf;
        }
    }
    for (int i = 0; i <= n; i++) {
        g[i].clear();
        g2[i].clear();
    }
    for (int i = 0; i < m; i++) {
        int a, b, w;
        cin >> a >> b >> w;
        g[a].push_back({b, w});
        g[b].push_back({a, w});
    }
    for (int i = 1; i <= n; i++) {
        cin >> bike[i];
    }
    // 在原始的圖上求每個點對得最短路
    for (int v = 1; v <= n; v++) {
        // 初始化
        for (int i = 1; i <= n; i++) {
            dist[i] = inf;
        }
        // 使用 dijkstra 演算法
        priority_queue<pii, vector<pii>, greater<pii>> pq;
        pq.push({0, v});
        dist[v] = 0;
        while (pq.size()) {
            auto now = pq.top(); pq.pop();
            if (now.ff > dist[now.ss]) continue;
            for (auto other : g[now.ss]) {
                if (dist[other.ff] > dist[now.ss] + other.ss) {
                    dist[other.ff] = dist[now.ss] + other.ss;
                    pq.push({dist[other.ff], other.ff});
                }
            }
        }
        // 建邊在新的圖上
        for (int i = 1; i <= n; i++) {
            if (i == v) continue;
            g2[v].push_back({i, dist[i] * bike[v]});
        }
    }
    // 初始化 (準備在新的圖上做最短路)
    for (int i = 1; i <= n; i++) {
        dist[i] = inf;
    }
    // 使用 dijkstra 演算法
    priority_queue<pii, vector<pii>, greater<pii>> pq;
    pq.push({0, 1});
    dist[1] = 0;
    while (pq.size()) {
        auto now = pq.top(); pq.pop();
        if (now.ff > dist[now.ss]) continue;
        for (auto other : g2[now.ss]) {
            if (dist[other.ff] > dist[now.ss] + other.ss) {
                dist[other.ff] = dist[now.ss] + other.ss;
                pq.push({dist[other.ff], other.ff});
            }
        }
    }
    // 輸出解
    cout << dist[n] << endl;
}
 
signed main() {
    int t;
    cin >> t;
    while (t--) sol();
}

推薦文章

題解 CSES Sum of Divisors

定義 sigmax 為 x 的所有因數之和 給予一個 n 求 sum^n{k=1}sigmak 之值以 12 為例 先看一下這張圖列出了 1 sim 12 的所有因數可以發現每兩個數字就會出現一次 2 每三次數字就會出現一次 3 觀察後可知 對於一個數字 x 它的出現次數會是 lfloor frac{n}{x} rfloor 因為每 x 個數字就會出現一個 x 的倍數 用這個做法枚舉每個數字計算就可以得到一個 On 的算法 但這樣還不夠快到足以處理這題 10^{12} 的資料量如果我們把剛剛那張圖的呈現方式改一下 上面那一橫排代表因數 1 sim 12 下面那一行排代表該因數出現的次數可以發現這張圖內有幾串連續的因數出現次數相同 因為事實上在計算這個的過程中只會出現最多 2 sqrt n 個相異數字 證明上面那張圖每個紅色圈起來代表一塊 如果我們可以塊為單位來計算就可以把時間複雜度降到 Osqrt{n} 了想像一下 如果 n 除以 x 可以剛好整除的話 令 k = lfloor frac{n}{x} rfloor 則 lfloor frac{n}{k} rfloor 必定等於 k 那如果不是剛好整除的呢? lfloor frac{12}{5} rfloor = 2 ,quadlfloor frac{12}{2} rfloor = 6 再試試看 6 lfloor frac{12}{6} rfloor = 2 ,quadlfloor frac{12}{2} rfloor = 6 觀察後發現 這裡的 6 就是最大的 x 滿足 frac{n}{x} 為 2 於是這樣我們就可以找到最後一個連續出現次數相同的因數 最後只要把每次一塊的第一個數和最後一個數用梯形公式求總和乘上共同的出現次數就可以得到答案了不過這邊要注意的是 因為答案很大所以題目要我們對 10^9 + 7 取模 但梯形公式中又會用到除法 所以我們不能直接除 需要先求出模反元素.modulo-inverse再乘進去才行cppusing namespace std;const int mod = 1e9 + 7;const int inv = 500000004; signed main { int n; cin n; int cur = 1; int ans = 0; while cur = n { int cnt = n cur; int last = n cnt; int sum = last + cur % mod last - cur + 1 % mod % mod inv % mod; ans += sum % mod cnt % mod % mod; cur = last + 1; } cout ans % mod endl;}

CSES

題解 CSES Common Divisors

找到兩個數字 使得兩數的最大公因數盡量大可以把題目想成 找到最大的 x 使得陣列中出現至少兩個他的倍數 於是我們可以先用 cnt 陣列紀錄每個數字的出現次數 然後枚舉每個可能的因數 假設目前枚舉到 k 從 k、2k、3k dots 計算陣列中存在多少個倍數 根據調和級數.harmonic-series可以算出時間複雜度為 On log ncppconst int maxn = 1e6 + 10;int cntmaxn;signed main { int n; cin n; vectorint vn; for int i = 0; i n; i++ { cin vi; cntvi++; } for int k = maxn; k 0; k-- { int cur = 0; for int j = k; j maxn; j += k { cur += cntj; } if cur = 2 { cout k endl; return 0; } }}

CSES

題解 CSES Restaurant Customers

給你 n 位客人的進入和離開餐廳的時間 輸出最多同時有幾位客人同時在餐廳- n le 2 times 10^5把每個人加入時間和離開的時間分別看成兩個時間點 並把所有時間點加入陣列後一起排序 然後用一個變數來維護目前餐廳人數 每次遇到加入就增加 反之遇到離開就減少 維護中遇到的最大值就是答案了 時間複雜度:Oncpp using namespace std; signed main { int n; cin n; vectorpii v; for int i = 0; i n; i++ { int s, t; cin s t; v.pushback{s, true}; v.pushback{t, false}; } sortv.begin, v.end; int cur = 0; int ans = 0; for auto e : v { if e.ss == true cur++; else cur--; ans = maxcur, ans; } cout ans endl;}

CSES