1. 程式人生 > 其它 >Codeforces Round #790 (Div. 4) 題解

Codeforces Round #790 (Div. 4) 題解

A. Lucky?

沒什麼好說的, 直接模擬即可.

#include <bits/stdc++.h>
using namespace std;
 
inline int read() {
    int x = 0; char c = getchar(); bool f = 1;
    while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
    while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return (f ? x : -x);
}
 
const int N = 1e5 + 10;
int n, a[N];
string s;
 
int main() {
    n = read();
    while(n--) {
        cin >> s;
        if(s[0] + s[1] + s[2] == s[5] + s[4] + s[3]) puts("YES");
        else puts("NO");
    } 
 
 
    return 0;
}

複雜度: \(O(n)\)

B. Equal Candies

由於每一盒的糖果只能增不能減, 並且要讓每一盒糖果都一樣, 顯然直接讓每一盒都等於 最小的糖果數 , 於是答案就等於 \(\sum _{i = 1} ^n a_i - n * \mathop{min\{a_i\}}\limits_{1 \leq i \leq n}\)

#include <bits/stdc++.h>
using namespace std;
 
inline int read() {
    int x = 0; char c = getchar(); bool f = 1;
    while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
    while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return (f ? x : -x);
}
 
const int N = 1e5 + 10;
int t, n, now, m, sum;
 
int main() {
    t = read();
 
    while(t--) {
        n = read();
        sum = 0; m = 0x3f3f3f3f;
 
        for(int i = 1; i <= n; ++i) {
            now = read();
            m = min(m, now);
            sum += now;
        }
 
        printf("%d\n", sum - n * m);
    } 
 
 
    return 0;
}

複雜度: \(O(n)\)

C. Most Similar Words

先看資料範圍: \(1 \leq t \leq 100, 2 \leq n \leq 50, 1 \leq m \leq 8\) .
這個範圍非常小, 於是考慮暴力.首先 \(O(n^2)\) 遍歷任意兩個字串, 再 \(O(m)\) 計算出距離,取最小值即可.

#include <bits/stdc++.h>
using namespace std;
 
inline int read() {
    int x = 0; char c = getchar(); bool f = 1;
    while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
    while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return (f ? x : -x);
}
 
const int N = 1e5 + 10;
int t, n, m, res;
string s[N];
 
int main() {
    t = read();
 
    while(t--) {
        res = 0x3f3f3f3f;
        n = read(); m = read();
        for(int i = 1; i <= n; ++i) cin >> s[i];
        for(int i = 1; i <= n ;++i) {
            for(int j = i + 1; j <= n; ++j) {
                int tot = 0;
                for(int p = 0; p < m; ++p)
                    tot += abs(s[i][p] - s[j][p]);
                res = min(res, tot);
            }
        }
 
        printf("%d\n", res);
    } 
 
 
    return 0;
}

複雜度: \(O(n^2m)\)

D. X-Sum

還是先看資料範圍: \(1 \leq t \leq 1000, 1 \leq n \leq 200, 1 \leq m \leq 200, 1 \leq \sum nm \leq 4 \times 10^4\).
注意最後一條 \(nm\) 的限制, 明顯提醒我們複雜度裡有 \(nm\).
於是先遍歷每一個格子就可以把 \(nm\) 弄出來, 再計算所有能攻擊到的格子總和 \(O(n + m)\)

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

inline int read() {
    int x = 0; char c = getchar(); bool f = 1;
    while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
    while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return (f ? x : -x);
}

const int N = 1e5 + 10;
int t, n, m, k, res, cnt, mp[205][205];

inline bool check(int x, int y) { return x >= 1 && x <= n && y >= 1 && y <= m; }//防止走出棋盤

int main() {
    t = read();

    while(t--) {
        n = read(); m = read(); k = max(n, m);
        res = 0;
        for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) mp[i][j] = read();
        for(int i = 1; i <= n ;++i) {
            for(int j = 1; j <= m; ++j) {
                cnt = 0;
                for(int p = 1; p <= k; ++p) {
                    if(check(i + p, j + p)) cnt += mp[i + p][j + p];
                    if(check(i - p, j - p)) cnt += mp[i - p][j - p];
                    if(check(i + p, j - p)) cnt += mp[i + p][j - p];
                    if(check(i - p, j + p)) cnt += mp[i - p][j + p];
                }

                res = max(res, cnt + mp[i][j]);
            }
        }

        printf("%d\n", res);
    } 


    return 0;
}

複雜度: \(O(nm(n+m))\)

E. Eating Queries

顯然, 每次查詢的時候, 他都會想吃掉 含糖量最高 的那一個, 所以我們需要從大到小排序.
又因為有多次查詢, 所以我們不必每次都重新算一遍, 於是 \(O(n)\) 維護一個數組 \(d\) 使得 \(\forall 1 \leq i \leq n, d_i = d_{i - 1} + a_i(a\ is\ sorted)\)
那麼每次查詢的時候只需要找到最小的 \(d_i\) 滿足 \(d_i \geq q(q\ is\ asking)\) , 看起來是不是很熟悉? 沒錯, 可以二分搜尋, 所以對於每次詢問只需要 \(log(n)\) 的時間了!

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

inline int read() {
    int x = 0; char c = getchar(); bool f = 1;
    while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
    while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return (f ? x : -x);
}

const int N = 1e6 + 10;
int t, n, m, q, res, a[N], d[N];

inline bool check(int x, int y) { return x >= 1 && x <= n && y >= 1 && y <= m; }

int main() {
    t = read();

    while(t--) {
        n = read(); m = read();
        for(int i = 1; i <= n; ++i) a[i] = read();
        sort(a + 1, a + 1 + n, greater<int>());
        for(int i = 1; i <= n; ++i) d[i] = d[i - 1] + a[i];

        while(m--) {
            res = lower_bound(d + 1, d + 1 + n, read()) - d;
            printf("%d\n",  res == n + 1 ? -1 : res);
        }
    } 


    return 0;
}

複雜度: \(O(nqlogn)\)

F. Longest Strike

首先顯然維護每個數出現的次數, 由於 \(1 \leq a_i \leq 10^9\) 所以不能使用桶, 於是使用 map \(n logn\) 統計(還有一個好處就是自動從小到大排序, 等下會用到).
然後再從小到大遍歷 map , 由於 map 從小到大, 正好滿足我們的需求, 可以順理成章地查詢一段最長的連續的區間, 然後更新.
思路很簡單, 程式碼很複雜. /wn

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

inline int read() {
    int x = 0; char c = getchar(); bool f = 1;
    while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
    while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return (f ? x : -x);
}

const int N = 1e6 + 10;
int t, n, m, l, r, maxl, minv, maxv, s, last, a[N];
//這裡使用m來代表題目中的k, maxl代表當前最長的區間長度, minv最小的ai, maxv最大的ai, s當前區間的長度, last上一個元素
map<int, int> mp;
//key元素, value出現次數

int main() {
    t = read();

    while(t--) {
        n = read(); m = read();
        mp.clear(); maxl = 0; minv = 0x3f3f3f3f; maxv = 0; s = 0;

        for(int i = 1; i <= n; ++i) a[i] = read(), ++mp[a[i]], minv = min(minv, a[i]), maxv = max(maxv, a[i]);
        last = minv - 1;//第一次認為是連續的
        for(auto v : mp) {
            int i = v.first;
            if(i == maxv && v.second >= m && maxl < s + 1 && i == last + 1) maxl = s + 1, l = i - s, r = i;//最後一個不一定更新的到, 需要特判, 但如果前面不連續, 這裡也判不掉, 在輸出的時候能解決

            if(v.second >= m && i == last + 1) ++s;//出現次數合格, 並且滿足連續
            else {
                if(maxl < s) maxl = s, l = last + 1 - s, r = last;//更新答案
                if(v.second >= m) s = 1;//如果次數合格但只是不連續, 則可以作為新一個區間的開頭
                else s = 0;//否則啥也不是, 從零開始
            }

            last = i;//記錄上一個元素
        }

        if(maxl) printf("%d %d\n", l, r);
        else if(mp[maxv] >= m) printf("%d %d\n", maxv, maxv);//最後不連續, 但是次數合格, 並且前面沒有任何一個次數合格的話, 輸出最後一個
        else puts("-1");//否則是真的無解
    } 


    return 0;
}

複雜度: \(O(nlogn)\)

G. White-Black Balanced Subtrees

我們規定 W 代表 \(1\) , B 代表 \(0\) , \(a_u\) 表示 \(u\) 這個節點的狀態(是 \(0\) 還是 \(1\)) , \(dp1_u\) 表示以 \(u\) 為根的子樹含有 \(1\) 的個數, \(dp0_u\) 表示以 \(u\) 為根的子樹含有 \(0\) 的個數, 不用腦子都能想出怎麼轉移:
\(dp1_u = \mathop{\sum dp_v} \limits_{v\ is\ u's\ son} + a_u\)
\(dp0_u = \mathop{\sum dp_v} \limits_{v\ is\ u's\ son} +\ !a_u\)
這裡的 \(!\) 表示程式裡的取反(原諒我不知道怎麼表示)
最後如果 \(u\) 滿足 \(dp1_u = dp0_u\) 時, 結果需要加 \(1\).

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

inline int read() {
    int x = 0; char c = getchar(); bool f = 1;
    while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
    while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return (f ? x : -x);
}

const int N = 1e6 + 10;
int t, n, res, dp1[N], dp0[N];
bool f[N];
string s;

int cnt = -1, head[N];
struct edge {
    int to, nxt;
} e[N << 1];

inline void add(int u, int v) {
    e[++cnt].to = v;
    e[cnt].nxt = head[u];
    head[u] = cnt;
}

inline void adde(int u, int v) { add(u, v); add(v, u); }

inline void dfs(int u, int fa) {
    if(f[u]) dp1[u] = 1;
    else dp0[u] = 1;

    for(int i = head[u]; ~i; i = e[i].nxt) {
        int v = e[i].to;
        if(v == fa) continue;
        dfs(v, u);
        dp1[u] += dp1[v];
        dp0[u] += dp0[v];
    }

    if(dp1[u] == dp0[u]) ++res;
}

int main() {
    t = read();
    while(t--) {
        n = read(); res = 0;
        for(int i = 0; i <= n; ++i) head[i] = -1;
        for(int i = 0; i <= n; ++i) dp1[i] = dp0[i] = 0;
        for(int i = 2; i <= n; ++i) adde(i, read());

        cin >> s;
        for(int i = 0; i < n; ++i)
            f[i + 1] = s[i] == 'W';

        dfs(1, 0);
        printf("%d\n", res);
    }

    return 0;
}

複雜度: \(O(n)\)

H1. Maximum Crossings (Easy Version)

這個題把線段換成點想必大家都會做, 那麼怎麼變換一下呢?
首先顯然如果 \(i < j\) 並且 \(a_i > a_j\) 必定相交(這個也就是逆序對)
那有的人說了: 給的樣例中不也有 \(a_i = a_j\) 相交的嗎?
當然, 如果 \(i < j\) 並且 \(a_i = a_j\) 可能相交.
考慮如下情況(只考慮1和3兩條線段, 2是為了美觀而存在的):

可以看到, 它們並沒有相交.

但我們改變一下相對位置, 就會驚奇地發現, 他們居然能相交!

又因為題目要求最大相交, 所以答案等於滿足 \(i < j, a_i \geq a_j\) 的個數.
這題 \(1 \leq n \leq 1000\) , 範圍較小, 所以可以用 \(O(n^2)\) 的雙指標來做.

#include <bits/stdc++.h>
using namespace std;
 
inline int read() {
    int x = 0; char c = getchar(); bool f = 1;
    while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
    while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return (f ? x : -x);
}
 
const int N = 1e6 + 10;
int t, n, res, a[N];
 
int main() {
    t = read();
 
    while(t--) {
        n = read(); res = 0;
        for(int i = 1; i <= n; ++i) a[i] = read();
        for(int i = 1; i <= n; ++i)
            for(int j = i + 1; j <= n; ++j)
                if(a[i] >= a[j]) ++res;
 
        printf("%d\n", res);
    } 
 
    return 0;
}

複雜度: \(O(n^2)\)

H2. Maximum Crossings (Hard Version)

這題與 H1 只差了一個\(1 \leq n \leq 2 \times 10^5\) .於是考慮優化.
這裡我用的樹狀陣列求的逆序對, 歸併排序也可以做.
注意要開 long long.

#include <bits/stdc++.h>
#define int long long
using namespace std;
 
inline int read() {
    int x = 0; char c = getchar(); bool f = 1;
    while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
    while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return (f ? x : -x);
}
 
const int N = 1e6 + 10;
int t, n, res, len, a[N], tree[N];
 
inline int lowbit(int &x) { return x & -x; }
inline void updata(int x) { while(x <= n) { ++tree[x]; x += lowbit(x); } }
inline int pre(int x) { int res = 0; while(x) { res += tree[x]; x -= lowbit(x); } return res; }
inline int ask(int l, int r) { return pre(r) - pre(l - 1); }
 
signed main() {
    t = read();
 
    while(t--) {
        n = read(); res = 0;
        for(int i = 1; i <= n; ++i) a[i] = read(), tree[i] = 0;
        for(int i = n; i >= 1; --i) {
            res += ask(1, a[i]);
            updata(a[i]);
        }
 
        printf("%lld\n", res);
    } 
 
    return 0;
}

複雜度: \(O(nlogn)\)