cf1046 div2 + 2024 ICPC Chengdu

cf1046 div2 and 2024 ICPC Chengdu

感觉每个题写完都要debug,不知道是不是听歌写题的原因……

div2

E

鉴定为不知道关键名词导致做不出题——边双连通分支:没有桥的极大连通子图(孤立点也可以看作是)。

首先考虑一棵树,可以发现未被赋值的点可以被赋任意值。

再考虑奇偶环。只考虑一位异或,手玩一下可以发现对于偶环,必须是全0或全1的,对于奇环,只能是全0的。从一位拓展到所有位,也就是对于偶环,只需要满足所有的值相等;而对于奇环,需要满足所有的值都是0。

于是我们先找到所有边双连通分支(根据定义可以发现,边双连通分支中,任意点都在一个环内)。根据我们发现的奇偶环规律,我们对于每个边双连通分支进行二分图染色检查是否存在奇环,再结合该分支中点值来判断这个边双连通分支的贡献,最后把贡献乘起来就好了。

ICPC Chengdu

B

比较暴力的想法是求出 \(f[i][x][y][z][0/1/2]\) 表示考虑前 \(i\) 个字符,a 类用了 \(x\) 个,b 类用了 \(y\) 个,c 类用了 \(z\) 个且最后一个字符是 a/b/c 的方案数。但是 dp 设计成这样肯定是不行的,就算加上滚动优化仍然存不下。但是注意我们可以省略掉 \(z\) 这一维,因为在前 \(i\) 个字符中 ? 的个数是确定的,而我们的 \(x+y+z\) 要等于这个数量,于是我们只用知道 \(x\) \(y\) 两个数就能确定 \(z\)。这样经过滚动优化就能存得下,时间复杂度也是合理的。转移也非常 trivial.。

现在的问题就来到了如何根据 \(f\) 来求答案。对于给定的 \(X\) \(Y\) \(Z\),我们发现实际上是要对 \(f[n \And 1]\) 这个数组的一个三角形区域求和(想的时候就卡在这里了)。若是一个正方形大家当然都会,直接二维前缀和。但实际上这个三角形区域也是能够进行二维前缀和的:

    for (int c = 0; c <= m; ++c) {
        // 离线询问,按照 c 的数量来分类
        if (qr[c].empty()) {
            continue;
        }

        for (int a = 0; a <= m; ++a) {
            for (int b = 0; b <= m; ++b) {
                g[a][b] = 0;
            }
        }
        // 初始化前缀和
        for (int a = 0; a <= m; ++a) {
            int b = m - a - c;
            if (b < 0) {
                continue;
            }

            g[a][b] = sum(f[n & 1][a][b]);
        }

        // 计算前缀和
        for (int a = 0; a <= m; ++a) {
            for (int b = 0; b <= m; ++b) {
                if (a + b + c <= m) {
                    continue;
                }

                g[a][b] = 0;
                if (b > 0) add(g[a][b], g[a][b - 1]);
                if (a > 0) add(g[a][b], g[a - 1][b]);
                if (a > 0 && b > 0) add(g[a][b], -g[a - 1][b - 1]);
                add(g[a][b], sum(f[n & 1][a][b]));
            }
        }

        for (auto &[a, b, idx] : qr[c]) {
            ans[idx] = g[a][b];
        }
    }

G

鉴定为观察力手玩的小题

I

对于一个不降的序列,我们可以发现任意的 k 都是可行的。但是如果一个序列可以被分成若干个极长不降子段,则k就有了限制:划分后不能有一个子段同时包含多个极长不降子段的元素。也就是说必须在除了最后一个极长不降子段的所有极长不降子段的结尾必须有一个划分点。于是 k 自然就是除了最后的所有极长不降子段长度的GCD的因数个数,用线段树维护就好了:

struct Info {
    int lv, ll; // 最左边的子段的第一个数和长度
    int rv, rl; // 最右边的子段的最后一个数和长度
    int num; // 极大的不减子段的数量
    int gcd; // 除去两边边子段,剩下的极大不减子段的长度的gcd

    Info(int val = 0) : lv(val), ll(1), rv(val), rl(1), num(1), gcd(0) {};
    Info(const Info &l, const Info &r) {
        lv = l.lv;
        ll = l.ll;
        rv = r.rv;
        rl = r.rl;
        num = l.num + r.num;
        gcd = std::__gcd(l.gcd, r.gcd);
        // 可以在中间合并一个段
        if (l.rv <= r.lv) {
            num -= 1;
            if (l.num == 1) {
                ll += r.ll;
            }
            if (r.num == 1) {
                rl += l.rl;
            }
            if (r.num != 1 && l.num != 1) {
                gcd = std::__gcd(gcd, l.rl + r.ll);
            }
        }
        else {
            if (l.num > 1) {
                gcd = std::__gcd(gcd, l.rl);
            }
            if (r.num > 1) {
                gcd = std::__gcd(gcd, r.ll);
            }
        }
    }

    Info operator + (const Info &r) {
        return Info(*this, r);
    }

    int getGCD() {
        return std::__gcd(gcd, ll);
    }
} tr[N << 2];

E

虽然没做出来,但想想确实挺直接的

K

我去我不会费用流也没有板子

posted @ 2025-10-04 23:32  Young_Cloud  阅读(13)  评论(0)    收藏  举报