Codeforces Round 962 (Div. 3) 補題記錄(A~G)

yhbqwq發表於2024-07-27

這場 Div. 3 難度高於平時。

A

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 500100;
int a[N];
signed main() {
    int T;
    scanf("%lld", &T);
    while (T--) {
        int n;
        scanf("%lld", &n);
        int cnt = n / 4;
        n %= 4;
        if (n) ++cnt;
        printf("%lld\n", cnt);
    }
    return 0;
}

B

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 500100;
int a[3010][3010];
signed main() {
    int T;
    scanf("%lld", &T);
    while (T--) {
        int n, m;
        scanf("%lld%lld", &n, &m);
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= n; ++j)
                scanf("%1lld", &a[i][j]);
        for (int i = 1; i <= n; i += m, puts(""))
            for (int j = 1; j <= n; j += m)
                printf("%lld", a[i][j]);
    }
    return 0;
}

C

clist 評分 \(1112\)

維護 \(26\) 個字母的字首和,然後計算給定區間兩個字串每一個字母的差的和,除以 \(2\) 就是答案。

時間複雜度為 \(O(n)\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 500100;
char s[N], t[N];
int box[N][26], bb[N][26];
signed main() {
    int T;
    scanf("%lld", &T);
    while (T--) {
        int n, q;
        scanf("%lld%lld%s%s", &n, &q, s + 1, t + 1);
        for (int i = 1; i <= n; ++i) {
            for (int j = 0; j < 26; ++j)
                box[i][j] = box[i - 1][j], bb[i][j] = bb[i - 1][j];
            ++box[i][s[i] - 'a'], ++bb[i][t[i] - 'a'];
        }
        while (q--) {
            int l, r;
            scanf("%lld%lld", &l, &r);
            int sum = 0;
            for (int i = 0; i < 26; ++i)
                sum += abs(box[r][i] - box[l - 1][i] - bb[r][i] + bb[l - 1][i]);
            printf("%lld\n", sum >> 1);
        }
    }
    return 0;
}

D

clist 評分 \(1420\)

問題轉化為求下列表示式的值:

\[\large \sum\limits_{x=1}^{n-2}\sum\limits_{y=1}^{n-x-y}[xy+x(n-x-y)+y(n-x-y)\le n] \]

其中 \([statments]\) 為艾弗森括號,若 \(statments\) 的值為 \(\texttt{true}\)\([statments]\) 的值為 \(1\),否則為 \(0\)

然後發現後面表示式是一個調和級數的形式,也就是說總的有效的答案數為 \(n\log n\) 級別。

因此直接暴力列舉即可。時間複雜度為 \(O(n\log n)\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 500100;
char s[N], t[N];
int box[N][26], bb[N][26];
signed main() {
    int T;
    scanf("%lld", &T);
    while (T--) {
        int n, x, cnt = 0;
        scanf("%lld%lld", &n, &x);
        for (int i = 1; i < x; ++i) {
            for (int j = 1; j < x - i; ++j) {
                int remain = (n - i * j) / (i + j);
                if (i * j >= n)
                    break;
                remain = min(remain, x - i - j);
                if (remain > 0)
                    cnt += remain;
            }
        }
        printf("%lld\n", cnt);
    }
    return 0;
}

E

clist 評分 \(1615\)

\(check(l,r)\) 表示 \(l\sim r\) 一段區間是否滿足條件。

因此可以開始推式子:

\(\large \sum\limits_{i=1}^n \sum\limits_{j=i+1}^n \sum\limits_{x=i}^j \sum\limits_{y=x}^j [check(x,y)]\)

\(\large \sum\limits_{x=1}^n \sum\limits_{y=x+1}^n \sum\limits_{i=1}^x \sum\limits_{j=y}^n [check(x,y)]\)

發現 \(check(x,y)\) 已經和後面的 \(i\)\(j\) 沒有關係了,把 \(check(x,y)\) 提到前面。

\(\large\sum\limits_{x=1}^n\sum\limits_{y=x+1}^n([check(x,y)]\sum\limits_{i=1}^x\sum\limits_{j=y}^n 1)\)

\(\large\sum\limits_{x=1}^n\sum\limits_{y=x+1}^n[check(x,y)]\times x\times(n-y+1)\)

然後根據括號的套路,將 \(1\) 看作 \(1\)\(0\) 看作 \(-1\) 跑一邊字首和。

\(x\)\(n\)\(1\) 倒著列舉,令 \(S_i\) 表示 \(i\) 的字首和。則計算左端點是 \(x\) 對答案造成的總貢獻就是所有滿足 \(y>x\)\(S_y=S_{x-1}\)\(x\times(n-y+1)\)。把 \(x\) 提取出來,後面的 \(n-y+1\)\(x\) 沒有關係可以直接扔到一個 map 裡算答案。

時間複雜度為 \(O(n\log n)\)。如果使用 gp_hash_table 一類雜湊表可以做到理論 \(O(n)\)一定一定記得取模。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 500100, mod = 1e9 + 7;
char s[N];
int sum[N];
signed main() {
    int T;
    scanf("%lld", &T);
    while (T--) {
        scanf("%s", s + 1);
        int n = strlen(s + 1);
        for (int i = 1; i <= n; ++i)
            if (s[i] == '1') sum[i] = sum[i - 1] + 1;
            else sum[i] = sum[i - 1] - 1;
        int res = 0;
        map<int, int> mp;
        for (int x = n; x; --x) {
            res += mp[sum[x - 1]] * x % mod;
            mp[sum[x]] += (n - x + 1);
            mp[sum[x]] %= mod;
            res %= mod;
        }
        printf("%lld\n", res % mod);
    }
    return 0;
}

F

clist 評分 \(1996\)

不是這明顯是虛高啊!這不比 E 簡單?

首先考慮一個 \(O((n+k)\log n)\) 的暴力。開一個堆來維護當前 \(n\) 個元素的值。每一次從堆裡取出當前值最大的元素,並將這個元素的值更新重新放回堆裡。

然後看到每一次取出堆裡最大的元素,因此可以發現答案一定存在一個閾值 \(p\),其中選取的數一定全部 \(\ge p\)。更具體的說:值 \(>p\) 的可以選取的數一定全部選取,值 \(<p\) 的可以選取的數一定全都不選取。值 \(=p\) 的數會選取一部分。

容易發現 \(p\) 具有單調性,因此考慮二分答案。計算 \(p\) 是否在 \(k\) 次以內可以取完可以使用等差數列求和。注意一下恰好為 \(p\) 的邊界情況即可。時間複雜度為 \(O(n\log n)\) 可以透過。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 500100, mod = 1e9 + 7;
int a[N], b[N];
int calc(int shouxiang, int gongcha, int xiangshu) {
    int moxiang = shouxiang - gongcha * (xiangshu - 1);
    return (shouxiang + moxiang) * xiangshu / 2;
}
signed main() {
    int T;
    scanf("%lld", &T);
    while (T--) {
        int n, k;
        scanf("%lld%lld", &n, &k);
        for (int i = 1; i <= n; ++i)
            scanf("%lld", &a[i]);
        for (int i = 1; i <= n; ++i)
            scanf("%lld", &b[i]);
        int l = 0, r = 1e9, best = 0;
        while (l <= r) {
            int mid = l + r >> 1;
            int cnt = 0;
            for (int i = 1; i <= n; ++i) {
                if (a[i] >= mid)
                    cnt += (a[i] - mid) / b[i] + 1;
            }
            if (cnt >= k)
                best = mid, l = mid + 1;
            else
                r = mid - 1;
        }
        int sum = 0;
        for (int i = 1; i <= n; ++i) {
            if (a[i] >= best + 1) {
                int p = (a[i] - (best + 1)) / b[i] + 1;
                sum += calc(a[i], b[i], p);
                k -= p;
            }
        }
        sum += k * best;
        printf("%lld\n", sum);
    }
    return 0;
}

G

clist 評分 \(2384\)

好逆天一題。考慮隨機賦權雜湊。

容易發現給出的問題即為一個大小為 \(n\) 的簡單環。從 \(a\)\(b\)\(a\neq b\))的不同簡單路徑數量一定為 \(2\)。把她放到一個圓上考慮,即為選取一段優弧或者劣弧並將其覆蓋。對於這樣的一組 \(a\)\(b\),考慮在整數範圍內隨機賦權 \(p\),並讓 \(a\)\(b\) 的點權全都異或上 \(p\)。然後跑一邊字首雜湊。此時可以驚人的發現,若兩個不同的區間所對應的權相同,那麼她們一定位於同一個可選塊內!!因此只需要找出最長的連續可選塊,然後此時貪心的讓這一段區間不被覆蓋,設這段長度為 \(p\) 則最優答案為 \(n-p\)。時間複雜度為 \(O(n)\)

#include <iostream>
#include <cstdio>
#include <random>
#include <map>
#define int long long
const int N = 500100, mod = 1e9 + 7;
int val[N];
std::mt19937_64 mt;
std::uniform_int_distribution<int> qwq(1, 400000000000000000);
signed main() {
    int T;
    scanf("%lld", &T);
    while (T--) {
        int n, m;
        scanf("%lld%lld", &n, &m);
        for (int i = 0; i <= n; ++i)
            val[i] = 0;
        for (int i = 0; i < m; ++i) {
            int a, b;
            scanf("%lld%lld", &a, &b);
            int t = qwq(mt);
            val[a] ^= t, val[b] ^= t;
        }
        std::map<int, int> mp;
        int mx = 0;
        for (int i = 1; i <= n; ++i)
            mx = std::max(mx, ++mp[val[i] ^= val[i - 1]]);
        printf("%lld\n", n - mx);
    }
}

相關文章