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;
}
posted @ 2022-04-18 15:38  Suzt_ilymtics  阅读(108)  评论(0编辑  收藏  举报