Codeforces Round 970 (Div. 3) ABCDEFGH

lulaalu發表於2024-09-03

來源:Codeforces Round 970 (Div. 3)

  • 標頭檔案
#include <bits/stdc++.h>
using namespace std;

#define YES "YES"
#define NO "NO"
#define Yes "Yes"
#define No "No"
#define F first
#define S second
#define int long long
#define ull unsigned long long
#define endl "\n"
#define IOS ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
#define rep(i, j, k) for (int i = (j); i < (k); i++)
#define all(x) x.begin(), x.end()
#define vi vector<int>
#define pii pair<int, int>

A. Sakurako's Exam

思路

看1能不能來抵消2,如果沒有1就看2的數量,有1就看是奇數還是偶數,如果是偶數就能抵消2

程式碼

string solve() {
    int a, b;
    cin >> a >> b;
    if (a == 0) {
        if (b % 2 == 0)
            return YES;
        else
            return NO;
    }
    if (a % 2 == 1) {
        return NO;
    } else {
        return YES;
    }
}

signed main() {
    IOS;
    int t = 1;
    cin >> t;
    while (t--) {
        cout << solve() << endl;
    }
    return 0;
}

B. Square or Not

思路

先看長度符不符合要求

再特判一下有沒有0的情況,除n=4外其他沒有0都輸出No

然後根據n求邊長,列舉所有點

程式碼

string s;
string solve() {
    int n;
    cin >> n >> s;
    if ((int)sqrt(n) * (int)sqrt(n) != n) {
        return No;
    }

    if (s.find('0') == string::npos) {
        if (n == 4)
            return Yes;
        else
            return No;
    }
    int a = (int)sqrt(n);
    for (int i = 0; i < a; i++) {
        for (int j = 0; j < a; j++) {
            char ch = s[i * a + j];
            char sch;// 應該是1或0
            if (i == 0 || j == 0 || i == a - 1 || j == a - 1)
                sch = '1';
            else
                sch = '0';
            if (ch != sch) {// 實際與理論比較一下
                return No;
            }
        }
    }
    return Yes;
}

signed main() {
    IOS;
    int t = 1;
    cin >> t;
    while (t--) {
        cout << solve() << endl;
    }
    return 0;
}

C. Longest Good Array

思路

最優情況就是從 \(l\) 開始,每個差值都差 \(1\)

然後開頭到結尾的就相差 \(1+2+3+...\) 所以用求和公式或者一個陣列就行

程式碼

const int N = 5e4;
int f[N];
int solve() {
    int l, r;
    cin >> l >> r;
    int len = r - l + 1;
    int ans = lower_bound(f, f + N, len) - f;
    return ans;
}

void pre() {
    f[0] = 0;
    f[1] = 1;
    for (int i = 2; i < N; i++) f[i] = f[i - 1] + i;
}

signed main() {
    IOS;
    int t = 1;
    pre();
    cin >> t;
    while (t--) {
        cout << solve() << endl;
    }
    return 0;
}

D. Sakurako's Hobby

思路

並查集,因為是全排列,就是好幾個圈圈,一開始還以為會出現環上帶線,那就麻煩了

程式碼

const int N = 2e5 + 10;
string s;
int fa[N];
int ans[N];

// 並查集
int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}

void merge(int x, int y) {
    fa[find(x)] = find(y);
}

void solve() {
    int n;
    cin >> n;

    rep(i, 1, n + 1) {
        fa[i] = i;
        ans[i] = 0;
    }

    rep(i, 1, n + 1) {
        int tmp;
        cin >> tmp;
        merge(i, tmp);
    }

    cin >> s;
    rep(i, 0, n) {
        // s的0表示黑色
        if (s[i] == '0') ans[find(fa[i + 1])]++;
    }

    rep(i, 1, n + 1) cout << ans[find(fa[i])] << " ";
    cout << endl;
}

E. Alternating String

思路

如果是偶數就把奇數上最多的保留,偶數位上最多的字母保留

如果是奇數,就維護字首和,然後遍歷每個位置

刪除當前位置後

如果想算奇數上的 \(a\) 字母,就是當前位置前的奇數位上的 \(a\) 的數量+當前位置後的偶數位上的 \(a\) 的數量

把奇數和偶數上的最大數算出來,再跟目前的最大數比較,和較大的更優

程式碼

int n;
string s;
const int N = 2e5 + 10;
int sum[26][2][N];// sum[i][j][k] 從0到k位,奇(1)/偶(0)位上字母i的數量
int solve() {
    cin >> n >> s;
    if (n % 2 == 0) {// 不刪,算數量就行
        map<int, int> ji_mp;
        map<int, int> ou_mp;
        for (int i = 0; i < n; i += 2) ou_mp[s[i] - 'a']++;
        for (int i = 1; i < n; i += 2) ji_mp[s[i] - 'a']++;

        int ji_mx = 0;
        int ou_mx = 0;
        for (int i = 0; i < 26; i++) {
            ji_mx = max(ji_mx, ji_mp[i]);
            ou_mx = max(ou_mx, ou_mp[i]);
        }
        return n - ji_mx - ou_mx;
    }
	// 初始化
    rep(i, 0, 26) {
        rep(j, 0, n + 2) {
            sum[i][0][j] = 0;
            sum[i][1][j] = 0;
        }
    }
    // 算累加
    for (int i = 0; i < n; i++) {
        if (i % 2 == 0)
            sum[s[i] - 'a'][0][i + 1] = 1;
        else
            sum[s[i] - 'a'][1][i + 1] = 1;
        for (int j = 0; j < 26; j++) {
            sum[j][0][i + 1] += sum[j][0][i];
            sum[j][1][i + 1] += sum[j][1][i];
        }
    }

    int ji_mx = 0;
    int ou_mx = 0;
    for (int i = 1; i <= n; i++) {
        int tjimx = 0, toumx = 0;
        // 求最大
        for (int j = 0; j < 26; j++) {
            int ji_num = sum[j][1][i - 1] + (sum[j][0][n] - sum[j][0][i]);
            int ou_num = sum[j][0][i - 1] + (sum[j][1][n] - sum[j][1][i]);
            tjimx = max(tjimx, ji_num);
            toumx = max(toumx, ou_num);
        }
        
        // 比較當前位置的最大和總的最大
        if (tjimx + toumx > ji_mx + ou_mx) {
            ji_mx = tjimx;
            ou_mx = toumx;
        }
    }
    return n - 1 - ji_mx - ou_mx + 1;
}

F. Sakurako's Box

思路

\([1,2,3,4,5]\) 為例,暴力會超時,所以維護字尾和,

\(1*(2+3+4+5)\)
\(2*(3+4+5)\)
\(3*(4+5)\)
\(4*5\)

然後總共抽 \(\frac{(n-1)n}{2}\) 次,這題大概主要是考逆元

程式碼

// int用long long來替,避免溢位
const int MOD = 1e9 + 7;
const int N = 2e5 + 10;
int ksm(int a, int b) {
    int res = 1;
    while (b) {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}
int sum[N]; // 字尾和
int a[N];
int solve() {
    int n;
    cin >> n;

    rep(i, 1, n + 1) {
        cin >> sum[i];
        a[i] = sum[i];
    }
    for (int i = n - 1; i >= 1; i--) {
        sum[i] += sum[i + 1];
        sum[i] %= MOD;
    }

    int s = 0;
    rep(i, 1, n) {
        int tmp = (a[i] * sum[i + 1]) % MOD;
        s = (s + tmp) % MOD;
    }
    int k = ((n - 1) * n / 2) % MOD;
    if (s % k == 0) return s / k;
    return (ksm(k, MOD - 2) * s) % MOD;
}

G. Sakurako's Task

思路

加來減去這種題,很可能跟gcd有關係,

首先分析一下,所有數的gcd一定小於等於最小值,比如gcd=2時,n=5,那麼一定可以透過這個2把陣列變成 \([0,2,4,6,8]\) 這種,在這種序列下的答案是最大的

特判一下n=1的情況

程式碼

int n, k;
int a[200005];
int solve() {
    cin >> n >> k;
    rep(i, 1, n + 1) cin >> a[i];

    if (n == 1) {
        if (k <= a[1])
            return k - 1;
        else
            return k;
    }
	
	// 求所有的gcd
    int GCD = 0;
    rep(i, 1, n + 1) GCD = __gcd(GCD, a[i]);

	// 如果k很大
    if ((n - 1) * (GCD - 1) < k) return n - 1 + k;
    int re = (k - 1) / (GCD - 1); // region
    /* 
    如gcd=3時,陣列是[0,3,6,9] ,k=1或2,位於0到3之間,k=4或5,位於3到6之間
    相當於位於區域0和區域1,每個區域大小是gcd-1
    */
   return re * GCD + (k - 1) % (GCD - 1) + 1;
}

H. Sakurako's Test

思路

最小中位數,就是把所有值都儘量縮小,那就所有值取%x

二分答案,答案一定在0到x之間

每次check檢視比mid小的值的數量,如果數量過多說明mid過大,需要減小,反之需要增加

以下是需要注意的:

  • 這個二分,有時候是r-1,l+1,有時候又不用,所以我一般是記錄答案,如果mid跟答案一樣就break,基本沒問題,也不用反覆嘗試
  • 這個check,如果每次遍歷,經過親身經歷,會超時,所以需要記錄數量,然後把每個%x後比mid小的區間加上,就是需要一個字首和來加速
  • 本題會重複詢問同一個值,需要記憶化答案

程式碼

int cnt[200010];
int n;
bool check(int mid, int q) {
    int small = 0;
    small += cnt[mid - 1];
    for (int i = 1; 1; i++) {
        if (mid - 1 + q * i >= n) {
            if (i * q - 1 <= n) small += cnt[n] - cnt[i * q - 1];
            break;
        }
        small += cnt[mid - 1 + q * i] - cnt[i * q - 1];
    }
    if (small <= n / 2)
        return true;
    else
        return false;
}
int t;
pii ans[100001];// 記憶化答案
void solve() {
    int m;
    cin >> n >> m;
    rep(i, 0, n + 1) cnt[i] = 0;
    rep(i, 0, n) {
        int x;
        cin >> x;
        cnt[x]++;
    }
    rep(i, 1, n + 1) cnt[i] += cnt[i - 1];
    rep(i, 0, m) {
        int q;
        cin >> q;
        if (ans[q].F == t) {
            cout << ans[q].S << " ";
            continue;
        }
        int l = 0, r = q;
        int mid;
        int res = 0;
        while (l < r) {
            mid = (l + r) / 2;
            if (check(mid, q)) {// 不需要思考,莽上去就行
                if (res == mid) break;
                res = mid;
                l = mid;
            } else {
                r = mid;
            }
        }
        cout << res << " ";
        ans[q] = {t, res};
    }
    cout << endl;
}

signed main() {
    IOS;
    for (int i = 0; i < 100001; i++) {
        ans[i] = {-1, -1};
    }
    cin >> t;
    while (t--) solve();
    return 0;
}

相關文章