1. 程式人生 > 其它 >Codeforces Round #782 (Div. 2) VP 記錄

Codeforces Round #782 (Div. 2) VP 記錄

目錄

賽時 4 題,E 是賽後補的 /kk

A. Red Versus Blue

題目保證了 \(b < r\),那麼也就是說 \(B\) 把整個序列分成了 \(B+1\) 份,然後每份中填入儘可能少的 \(R\)

最優的方式顯然是儘可能去均勻分配,每份填入 \(\frac{R}{B+1}\)\(R\),如果有剩下的 \(R\) 沒填,那每份多填一個即可。

void Main() {
    n = read(), R = read(), B = read();
    int p = R / (B + 1), q = R % (B + 1);
    for(int j = 1; j <= p + (1 <= q); ++j) cout << "R";
    for(int i = 1; i <= B; ++i) {
        cout << "B";
        for(int j = 1; j <= p + (i + 1 <= q); ++j) cout << "R";
    } 
    puts("");
}

signed main() {
    T = read();
    while(T--) Main();
	return 0;
}

B. Bit Flipping

反向考慮,每個位置都會被異或 \(K\) 次,每次選中一個位置就是讓它少異或一次。

從前向後貪心看看能不能讓儘可能多的位置異或成 \(1\) 即可,多餘的操作次數全扔到最後一位。

void Main() {
    n = read(), K = read(); int lst = K;
    for(int i = 1; i <= n; ++i) scanf("%1d", &a[i]), cnt[i] = 0;
    for(int i = 1; i <= n; ++i) {
        if((K & 1) && a[i] && lst) lst--, cnt[i] = 1;
        if(!(K & 1) && !a[i] && lst) lst--, cnt[i] = 1;
    }
    if(lst) cnt[n] += lst;
    for(int i = 1; i <= n; ++i) printf("%d", a[i] ^ ((K - cnt[i]) & 1)); puts("");
    for(int i = 1; i <= n; ++i) printf("%d ", cnt[i]); puts("");
}

signed main() {
    T = read();
    while(T--) Main();
	return 0;
}

C. Line Empire

第一眼可以設一個 \(f_{i,j}\) 表示基地在第 \(j\) 個位置,並且走完了前 \(i\) 個位置的最小花費。

\[f_{i,j} = \min_{k\le j < i} \{ f_{i-1,k} + A|a_j-a_k| + B|a_i-a_j| \} \]

不過這樣轉移是 \(n^3\) 的,顯然過不了。

考慮這個題目自身的性質,發現如果基地最後走到了第 \(i\) 個位置,那麼在佔領第 \(i\) 個位置之前都可以把基地移動到第 \(i-1\) 個位置然後再去佔領第 \(i\) 個位置,而對於第 \(i\) 個之後的位置都只能從第 \(i\)

個位置出發去佔領。也就是說如果我們確定了最後基地最後走到了哪個位置,那麼整個過程的最優方案我們也是確定的。

所以我們只需要列舉基地最後在哪個位置,然後通過預處理一些需要的東西就可以直接 \(O(1)\) 算出貢獻了。

void Main() {
    n = read(), A = read(), B = read(), sum[n + 1] = 0;
    for(int i = 1; i <= n; ++i) a[i] = read();
    for(int i = n; i >= 1; --i) sum[i] = sum[i + 1] + a[i];
    int ans = INF;
    for(int i = 0; i <= n; ++i) ans = min(ans, A * a[i] + B * a[i] + B * (sum[i + 1] - a[i] * (n - i)));
    cout << ans << "\n";
}

signed main() {
    T = read();
    while(T--) Main();
	return 0;
}

D. Reverse Sort Sum

可以從前往後考慮這個位能不能填 \(1\)

如果之前填了 \(x\)\(1\),當前考慮到第 \(i\) 位。

那麼在這裡填 \(1\) 之後,會對 \([i-x,n-x]\) 這段區間有一個 \(1\) 的貢獻,並且在這之前這個 \(1\) 還會對 \(i\) 這個位置產生 \(i-1\) 次貢獻。

區間加減操作直接上線段樹。

假設這裡填 \(1\),如果減去它產生的貢獻後發現最小值出現了負數,說明這裡應該填 \(0\),否則說明這裡可以填 \(1\)

正確性大概就是每個位置 \(1\) 的貢獻都是越來越靠後的?就是說後面的 \(1\) 不會對前面的造成貢獻,所以可以從前向後確定。

namespace Seg {
    #define lson i << 1
    #define rson i << 1 | 1
    int Min[MAXN << 2], lazy[MAXN << 2];
    void Push_up(int i) { Min[i] = min(Min[lson], Min[rson]); }
    void Build(int i, int l, int r) {
        lazy[i] = Min[i] = 0;
        if(l == r) return Min[i] = a[l], void();
        int mid = (l + r) >> 1;
        Build(lson, l, mid), Build(rson, mid + 1, r);
        Push_up(i);
    }
    void Push_down(int i) {
        if(!lazy[i]) return ;
        lazy[lson] += lazy[i], lazy[rson] += lazy[i];
        Min[lson] -= lazy[i], Min[rson] -= lazy[i];
        lazy[i] = 0;
    }
    void Modify(int i, int l, int r, int L, int R, int val) {
        if(L <= l && r <= R) return lazy[i] += val, Min[i] -= val, void();
        Push_down(i); int mid = (l + r) >> 1;
        if(mid >= L) Modify(lson, l, mid, L, R, val);
        if(mid < R) Modify(rson, mid + 1, r, L, R, val);
        Push_up(i);
    }
    int Query(int i, int l, int r, int L, int R) {
        if(L <= l && r <= R) return Min[i];
        Push_down(i); int mid = (l + r) >> 1, ans = INF;
        if(mid >= L) ans = min(ans, Query(lson, l, mid, L, R));
        if(mid < R) ans = min(ans, Query(rson, mid + 1, r, L, R));
        return ans;
    }
}

void Main() {
    n = read();
    for(int i = 1; i <= n; ++i) a[i] = read();
    Seg::Build(1, 1, n);
    int lst = 0;
    for(int i = 1; i <= n; ++i) {
        int L = i - lst, R = i - lst + n - i;
        Seg::Modify(1, 1, n, L, R, 1);
        Seg::Modify(1, 1, n, i, i, i - 1);
        int p = Seg::Query(1, 1, n, L, R);
        int q = Seg::Query(1, 1, n, i, i);
        if(p >= 0 && q >= 0) {
            b[i] = 1, lst ++;
        } else {
            b[i] = 0;
            Seg::Modify(1, 1, n, L, R, - 1);
            Seg::Modify(1, 1, n, i, i, - i + 1);
        }
    }
    for(int i = 1; i <= n; ++i) cout << b[i] << " "; puts("");
}

signed main() {
    T = read();
    while(T--) Main();
	return 0;
}

E. AND-MEX Walk

胡亂手模可以發現答案只能是 \(0,1,2\),因為做 \(\&\) 字首和的話 \(1,2\) 不能同時出現。

看到位運算想到拆位。因為點可以重複經過,所以可以往聯通塊的方向上去想。

考慮答案為 \(0\) 的情況,就是存在一條 \(u \to v\) 的路徑,這個路徑上的所有邊至少有一位全部都為 \(1\)

那麼我們根據邊的權值,對於每一位用一個並查集,如果邊 \((u,v)\)\(w\) 的這一位為 \(1\),那麼可以在並查集中將 \(u,v\) 合併。

那答案為 \(0\) 的情況就變成了判斷是否存在一位的並查集中 \(u,v\) 屬於同一個連通塊。

考慮答案為 \(1\) 的情況。首先要建立在答案不是 \(0\) 之前。

然後,我們要在 \(\&\) 字首和變為 \(0\) 之前不能出現讓其 \(1\),我們可以在先保證二進位制中的某一位都為 \(1\) 之前,先把二進位制中的第一位的 \(1\) 消掉。

那麼,對於所有邊權為偶數的邊,對每一位所在的連通塊打上標記。

如果存在一位中 \(u\) 這個點的聯通塊被打了標記,那麼說明上面的條件可以做到,也就是說答案為 \(1\)

否則答案為 \(0\)

struct node { int u, v, w; }e[MAXN];
int n, m, Q;
struct Graph {
    int fa[MAXN];
    bool vis[MAXN];
    void Init() { for(int i = 1; i <= n; ++i) fa[i] = i; }
    int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
    void Add(int x, int y) {
        int uf = find(x), vf = find(y);
        if(uf != vf) fa[uf] = vf;
    }
    bool Query(int x, int y) { return find(x) == find(y); }
}G[31]; 

signed main() {
    n = read(), m = read();
    for(int i = 0; i <= 30; ++i) G[i].Init();
    for(int i = 1, u, v, w; i <= m; ++i) {
        e[i].u = read(), e[i].v = read(), e[i].w = read();
        for(int j = 0; j <= 30; ++j) {
            if((e[i].w >> j) & 1) {
                G[j].Add(e[i].u, e[i].v);
            }
        }
    }
    for(int i = 1; i <= m; ++i) {
        if(e[i].w & 1) continue;
        for(int j = 1; j <= 30; ++j) {
            G[j].vis[G[j].find(e[i].u)] = true;
            G[j].vis[G[j].find(e[i].v)] = true;
        }
    }
    Q = read();
    for(int i = 1, u, v; i <= Q; ++i) {
        u = read(), v = read();
        bool flag = false;
        for(int j = 0; j <= 30; ++j) flag |= (G[j].Query(u, v));
        if(flag) { puts("0"); continue; }
        for(int j = 1; j <= 30; ++j) flag |= G[j].vis[G[j].find(u)];
        flag ? puts("1") : puts("2");
    }
	return 0;
}