Codeforces Round 959 sponsored by NEAR (Div. 1 + Div. 2)

Sakura17發表於2024-07-20

題目連結:Codeforces Round 959 sponsored by NEAR (Div. 1 + Div. 2)

總結:Bwa兩發,C讀假題。發揮很一般,補題感覺到E都是能做的,紅溫。

A. Diverse Game

fag:簽到

B. Fun Game

fag:位運算 + 思維

Description:給定一個兩個\(01\)字串\(s, t\),對於\(s\),每次可以選擇一個區間\(l, r\),令\(s_i \wedge s_{i - l + 1}\)替換\(s_i, l <= i <= r\)。問能否將

\(s\)轉化為\(t\)

Solution:1與(0, 1)異或會將其改變, 0與任何數異或為0

  • 那麼假設\(s\)串中第一個不同的位置前面有\(1\),那麼後面的數都可以與這個數異或,從而變得相等。如果沒有但是\(s_i\)
    \(1\),則可以自己變為\(0\),或者不行。
  • 即看\(s,t\)串中誰先出現\(1\),因為後面的數可以依靠的數改變。

Competing:有點慌亂,亂交了兩發。難繃

void solve() {
    int n;
    cin >> n;
    
    string s, t;
    cin >> s >> t;

    for (int i = 0; i < n; i++) {
        if (s[i] == '1') {
            cout << "Yes\n";
            return;
        }
        if (t[i] == '1') {
            cout << "No\n";
            return;
        }
    }
    cout << "Yes\n";
}

C. Hungry Games

fag: 字首和 + 二分

Description:給定\(n\)個數和一個\(x\)。選擇一個區間\(l, r\),玩家的毒性\(g\),初始為\(0\),首先\(g += a_i\);如果\(g <= x\)遊戲繼續,否則\(g = 0\),遊戲繼續。求有多少個子區間使玩家毒性最終不為\(0\)

Solution:一般求區間個數,我會考慮字首和或者雙指標,因為所求區間個數往往很大。

  • 我們簡單模擬下,開始\(g\)不斷增大,然後變為\(0\),然後再增大。那麼對於變為\(0\)之後再增大這部分是不是等價於以這個位置為起點,進行相同的操作。顯然我們暴力求解會計算很多相同的狀態,考慮最佳化。
  • 我們用\(cnt[i]\)表示以這個點為起點的方案數。那麼\(cnt[i] = (j - i) + cnt[j + 1]\),其中\(j\)是變為\(0\)的位置。
  • 我們需要用後面的位置更新前面的位置,所以我們從後面開始計算。那麼怎麼計算\(j\)的位置呢?因為\(g\)是遞增的,考慮二分。
void solve(){
    int n, x;
    cin >> n >> x;
    vector<int> a(n + 5, 1e18), s(n + 5, 1e18);

    s[0] = 0;
    for (int i = 1; i <= n; i ++){
        cin >> a[i];
        s[i] = s[i - 1] + a[i];  // 字首和
    }

    vector<int> cnt(n + 5);
    LL ans = 0;
    for (int i = n; i; i --){
        // 我們從後往前計算答案,cnt[i]表示以i為起點有多少符合條件的子串
        // 我們從i點開始找到第一個不符合條件的點(第一個子串的結尾)
        int idx = lower_bound(s.begin(), s.end(), s[i - 1] + x + 1) - s.begin();
        // 但是它在idx處清0了,因此從idx + 1起又開始從新計算答案(但是我們已經記錄下來了)
        cnt[i] += cnt[idx + 1] + (idx - i);
        ans += cnt[i];
    }
    cout << ans << endl;
}

D. Funny Game

fag:並查集 + 鴿巢原理

Solution:給定\(n\)個數,有\(n - 1\)操作,每次操作可以選擇兩個數\(a_u, a_v\),滿足\(|a_u - a_v|\)\(x\)整除(假設這是第\(x\)次操作)然後將這兩個點相連,問最後能否形成一個連通圖,如果能給出操作序列。

Description:樣例全是\(Yes\),考慮是否一定有解。然後從後往前操作比從前往後操作更優(可能滿足後面操作的數被前面選中)。假設當前有\(n\)個數,對\(n - 1\)取餘後,根據鴿巢原理一定有兩個數餘數相等。因此能夠選出符合條件的數。然後將這兩個數合併,還剩\(n - 1\)個數,對\(n - 2\)取餘,同理是有解的。

  • 考慮每次如何保證每次取的數是兩個不同的連通塊,使用並查集維護資訊,每次只取代表節點即可(\(f[x] == x\))。
int f[N];

void init(){
    for (int i = 0; i < N; i ++)
        f[i] = i;
}

int find(int x){
    if (x != f[x])
        f[x] = find(f[x]);

    return f[x];
}

bool merge(int x, int y){
    int fx = find(x), fy = find(y);
    if (fx == fy)
        return false;
    
    f[fx] = fy;
    return true;
}

void solve(){
    cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i ++){
        cin >> a[i];
    }

    vector<pii> ans(n);
    for (int i = n - 1; i; i --){
        vector<int> p(n, -1);
        for (int j = 1;  j <= n; j ++){
            if (find(j) == j){
                int r = a[j] % i;
                if (p[r] != -1){
                    merge(p[r], j);
                    ans[i] = {p[r], j};
                    break;
                }
                else
                    p[r] = j;
            }
            
        }
    }
    cout << "Yes\n";
    for (int i = 1; i < n; i ++){
        cout << ans[i].fi << " " << ans[i].se << endl;
    }

}

E. Wooden Game

fag:貪心 + 二進位制

Description:有\(k\)棵樹,每次操作可以任意刪去一顆子樹,可以操作任意次。求刪去子樹大小按位或的最大值。

Solution:題目求按位或的最大值,我們顯然要想到位運算。

  • 對於每一棵樹,如果他的大小為\(a_i\),那麼我們可以取\([0, a_i]\)的任意一個值出來運算,只需要刪去一定數量的葉子節點。

  • 等價於每個數的可以為\([0, a_i]\),求它們異或的最大值。

  • 我們從最高位開始一位一位看,如果該位有兩個數都是\(1\),那麼該位可以取\(1\),後面的所有位都可以取\(1\)。將其中一個數減\(1\)即可。

  • 該位只有一個數為\(1\),那麼該位取\(1\)

void solve(){
    cin >> n;

    vector<int> a(n);
    vector<int> cnt(32);
    for (int i = 0; i < n; i ++){
        cin >> a[i];
        for (int j = 0; j < a[i] - 1; j ++){
            int x;
            cin >> x;
        }
        for (int j = 31; ~j; j --){
            if ((a[i] >> j) & 1)
                cnt[j] ++;
        }
    }

    int ans = 0;
    for (int i = 31; ~i; i --){
        if (cnt[i] >= 2){
            ans |= (1LL << (i + 1)) - 1;
            break;
        }
        else if (cnt[i]){
            ans |= 1LL << i;
        }
    }
    
    cout << ans << endl;
}

相關文章