Codeforces Round #782 (Div. 2) VP 记录

赛时 4 题,E 是赛后补的 /kk

A. Red Versus Blue

题目保证了 b<r,那么也就是说 B 把整个序列分成了 B+1 份,然后每份中填入尽可能少的 R

最优的方式显然是尽可能去均匀分配,每份填入 RB+1R,如果有剩下的 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

第一眼可以设一个 fi,j 表示基地在第 j 个位置,并且走完了前 i 个位置的最小花费。

fi,j=minkj<i{fi1,k+A|ajak|+B|aiaj|}

不过这样转移是 n3 的,显然过不了。

考虑这个题目自身的性质,发现如果基地最后走到了第 i 个位置,那么在占领第 i 个位置之前都可以把基地移动到第 i1 个位置然后再去占领第 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

如果之前填了 x1,当前考虑到第 i 位。

那么在这里填 1 之后,会对 [ix,nx] 这段区间有一个 1 的贡献,并且在这之前这个 1 还会对 i 这个位置产生 i1 次贡献。

区间加减操作直接上线段树。

假设这里填 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 的情况,就是存在一条 uv 的路径,这个路径上的所有边至少有一位全部都为 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 @   Suzt_ilymtics  阅读(115)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示