杂题选谈
2025-11-18
暂无标签

古人总说:『日出而作,日落而息』。既然如此,在冬天的阴天就应该安眠一个下午。

但这样总有被误认为冬眠动物的风险。实际上是全年无休地努力摸鱼着!


C. meat

https://www.becoder.com.cn/contest/6752/problem/3

给定一个 \(n\) 个点 \(m\) 条边的简单无向图,点有点权,问图中所有四元环的权值和,对 \(10^9+7\) 取模。

\(n,m\le 10^5\)

  • 容易想到 meet in middle 的做法:把四元环 \((1,2,3,4)\) 拆解为 \(1-2-3\)\(1-4-3\) 两个部分,从 \(1\) 出发,找到所有和 \(1\) 的距离为 \(2\) 的点 \(3'\),在 \(3\) 处完成统计。

  • 做这么一件事:在每次『从 \(u\) 出发,找到所有和 \(u\) 的距离为 \(2\) 的点』之后,从图中删去 \(u\)。容易发现删点不会影响正确性。

    注意这里要用 BFS 而非 DFS 保证复杂度。

  • 钦定按点的度数从大到小做这个过程,则复杂度为 \(O(m\sqrt m)\)。证明参考三元环,考虑根号分治:

    • 当点 \(u\) 的度数 \(deg_u>\sqrt m\) 时,由于 \(\sum deg=2m\),这样的点有 \(O(\sqrt m)\) 个;暴力 BFS 两步时,最劣复杂度为 \(O(m)\)。故该种情况下复杂度为 \(O(m\sqrt m)\)

    • 否则,\(deg_u\le \sqrt m\) 时,由于度数更大的都被删除了,\(u\) 能 BFS 到的点度数一定比 \(u\) 更小。

      注意区分这一步和三元环重定向的区别:此时仍然允许在第 2 个点 \(\to\) 第 3 个点时,由小度数走向大度数,否则无法完全统计。三元环能重定向的原因是,不管如何重定向,三元环中总能找到一条 \(a\to b\to c\) 的有向路径。

      故此时两步 BFS 的复杂度上界为 \(O({deg_u}^2)\)

      考虑整体复杂度上界,即找到一种对 \(2m\) 的划分方式,使得每一个元素不超过 \(\sqrt m\),且最大化 \(\sum {deg_u}^2\)。最劣情况显然是全部取 \(\sqrt m\),此时复杂度为 \(O(m\sqrt m)\)

    综上,整体复杂度为 \(O(m\sqrt m)\),而且非常不满。

  • By the way, 无向图三元环计数?

    按照大度数 \(\to\) 小度数对边重定向。由于三元环需要的信息更少,只需要 check『走两步能走到的点』和『走一步能走到的点』的交集,但仍需要两步 BFS。

    根号分治证明复杂度,证明过程几乎一致。不赘述。

#include <bits/stdc++.h>
const int mod = 1e9 + 7;
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
    std::freopen("meat.in", "r", stdin);
    std::freopen("meat.out", "w", stdout);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    int n, m;
    std::cin >> n >> m;
    std::vector<long long> a(n + 1);
    for (int i = 1; i <= n; ++i)
        std::cin >> a[i];
    std::vector<int> deg(n + 1), id(n);
    std::vector<std::vector<int> > g(n + 1);
    for (int x, y; m--; ) {
        std::cin >> x >> y;
        ++deg[x], ++deg[y];
        g[x].push_back(y), g[y].push_back(x);
    }
    std::iota(id.begin(), id.end(), 1);
    std::sort(id.begin(), id.end(), [&](int i, int j) { return deg[i] > deg[j]; });
    std::vector<long long> s(n + 1);
    std::vector<int> tag(n + 1), cnt(n + 1);
    auto res = 0ll;
    for (auto i : id) {
        std::vector<int> pos;
        tag[i] = 1;
        for (auto j : g[i]) 
            if (!tag[j])
                for (auto k : g[j])
                    if (!tag[k]) {
                        pos.push_back(k);
                        ++cnt[k], (s[k] += a[j]) %= mod;
                    }
        for (auto j : pos)
            if (cnt[j]) {
                res += s[j] * (cnt[j] - 1) % mod;
                res += (a[i] + a[j]) % mod * (cnt[j] * (cnt[j] - 1) / 2 % mod) % mod;
                cnt[j] = s[j] = 0;
            }
        res %= mod;
    }
    std::cout << res << '\n';
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

B. port

https://www.becoder.com.cn/contest/6767/problem/2

给定 \(n\times n\) 的棋盘,初始放置了 \(k\) 个棋子 \((x_i, y_i)\)。现在可以额外放置若干棋子,问有多少种方案满足:

  • 在最终的棋盘上,任意 \(m\times m\) 的矩形中,恰有一个棋子。

\(998244353\) 取模。

多测,\(T\le 2\times 10^5,m\le n\le 10^6,k\le \min(n^2,2\times 10^5),\sum k\le 2\times 10^5\)

  • 需要高强度手玩,然后概括出以下事实:

    • 两个棋子不冲突的充要条件:行差为 \(m\) 的倍数或列差为 \(b\) 的倍数。
    • 初始棋子不冲突的充要条件:任意两个棋子满足上一条。
    • 合法局面的充要条件:每一行的棋子的列差均为 \(m\),或每一列的棋子行差均为 \(m\)

    这样,只有第一行 / 列的选取是相对自由的。由此可以得到 \(k=0\) 时的答案:

    \[ 2\times(m^{\left\lfloor\frac{n}{m}\right\rfloor}\times(m-n\bmod m)+m^{\left\lfloor\frac{n}{m}\right\rfloor+1}\times(n\bmod m))-m^2 \]

  • 对于 \(k\ne 0\) 的情况,统计两种填数方式一路上可以塞数的位置,顺便判定合法性即可。

#include <bits/stdc++.h>
const int mod = 998244353;
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
    std::freopen("port.in", "r", stdin);
    std::freopen("port.out", "w", stdout);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    int T;
    auto qkp = [&](long long first, int second) {
        auto res = 1ll;
        for (; second; (first *= first) %= mod, second >>= 1)
            if (second & 1)
                (res *= first) %= mod;
        return res;
    };
    for (std::cin >> T; T--; ) {
        int k;
        long long n, m;
        std::cin >> n >> m >> k;
        std::vector<std::pair<int, int> > a(k + 1);
        for (int i = 1; i <= k; ++i)
            std::cin >> a[i].first >> a[i].second;
        auto res = 0ll;
        if (m == 1) {
            std::cout << 1 << '\n';
            continue;
        }
        if (k == 0) {
            auto res = qkp(m, n / m) * (m + mod - n % m) % mod + qkp(m, n / m + 1) * (n % m) % mod;
            res = 2 * res % mod + mod - m * m % mod;
            std::cout << res % mod << '\n';
            continue;
        }
        std::sort(a.begin() + 1, a.end());
        int la = 0, now = 0;
        bool flag1 = true, flag2 = true;
        for (int i = 1, j = 1; i <= k; i = j + 1) {
            now = now + std::max(0ll, (a[i].first - la - 1) / m);
            if (la && (a[i].first - la) % m != 0)
                flag1 = false;
            la = a[i].first;
            for (j = i; j <= k && a[i].first == a[j].first; ++j);
            --j;
            for (int k = i + 1; k <= j; k++)
                if ((a[k].second - a[k - 1].second) % m != 0)
                    flag1 = false;
            i = j;
        }
        now += (n - la) / m;
        if (flag1)
            (res += qkp(m, now)) %= mod;
        la = 0, now = 0;
        std::sort(a.begin() + 1, a.end(), [&](auto &x, auto &y) { return x.second == y.second ? x.first < y.first : x.second < y.second; });
        for (int i = 1, j = 1; i <= k; i = j + 1) {
            now = now + std::max(0ll, (a[i].second - la - 1) / m);
            if (la && (a[i].second - la) % m != 0)
                flag2 = false;
            la = a[i].second;
            for (j = i; j <= k && a[i].second == a[j].second; ++j);
            --j;
            for (int k = i + 1; k <= j; k++)
                if ((a[k].first - a[k - 1].first) % m != 0)
                    flag2 = false;
            i = j;
        }
        now += (n - la) / m;
        if (flag2)
            (res += qkp(m, now)) %= mod;
        if (flag1 && flag2)
            (res += mod - 1) %= mod;
        std::cout << res << '\n';
    }
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

C. west

https://www.becoder.com.cn/contest/6767/problem/3

给定长度为 \(n\) 的两个栈 A, B,两个栈内的元素分别是 \(n\) 的一个排列。通过执行以下操作构造一个序列:

  • (当 A 剩余元素不为 1 时)将 B 的栈顶加入序列,并弹出 A 的栈顶。
  • (当 B 剩余元素不为 1 时)将 A 的栈顶加入序列,并弹出 B 的栈顶。

容易发现最后的序列长总为 \(2n-2\)。问可以构造出多少种序列。对 \(998244353\) 取模。

\(n\le 5000\)

机房里只有 1 个神仙、1 个人切了 T3。总算是得瑟上一回了!

  • 发现:如果存在两种操作方式,使得它们结果序列的 \(1\sim i\) 项相同,那么可以转化为,它们的 \(1\sim i-1\) 相同,且第 \(i\) 项相同。

  • \(f_{i,j,0/1/2}\) 表示结果序列的第 \(i\) 项为 \(j\),这个 \(j\) 由 A 贡献 / 由 B 贡献 / AB 都可以贡献的方案数。

    发现用 \(i,j,0/1/2\) 三个信息可以唯一确定当前 A B 两个栈的状态(利用排列这个条件),然后就可以填表转移了。

  • 需要注意的地方:\(f_{i,j,2}\) 用 A, B 反推可能得到不一样的结果(设为 \((A_1,B_1)/(A_2,B_2)\)),此时两种结果都有可能分岔 / 合并,共 \(2\times 2=4\) 种组合方式。

    利用 \(A_1\ne A_2\land B_1\ne B_2\) 这一点可以发现,不会出现用 \(A_1,A_2,B_1,B_2\) 中的超过两个转移得到同样结果的情况。

    反过来,可以像这里一样设 \(f_{*,*,2}\) 是因为我们默认了,如果一个状态可以通过多种转移路径得到,那么一定是恰好两种。

    虽然比官方做法简单 10086 倍,但似乎过于凑巧了。

  • 最后的答案为 \(\sum\limits_i f_{2n-2,i,*}\)

一定要注意用 A, B 转移对应的条件是 B, A。

#include <bits/stdc++.h>
const int mod = 998244353;
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
    std::freopen("west.in", "r", stdin);
    std::freopen("west.out", "w", stdout);
    auto stime = std::chrono::steady_clock::now();
    int n;
    std::cin >> n;
    std::vector<int> a(n + 1), b(n + 1), pa(n + 1), pb(n + 1);
    for (int i = 1; i <= n; ++i)
        std::cin >> a[i], pa[a[i]] = i;
    for (int i = 1; i <= n; ++i)
        std::cin >> b[i], pb[b[i]] = i;
    using arr = std::vector<long long>;
    using brr = std::vector<arr>;
    using crr = std::vector<brr>;
    crr f(2, brr(n + 1, arr(3)));
    if (a[1] == b[1])
        f[1][a[1]][2] = 1ll;
    else
        f[1][a[1]][0] = f[1][b[1]][1] = 1ll;
    for (int i = 1, k = 1; i < 2 * n - 2; ++i, k ^= 1) {
        for (int j = 1; j <= n; ++j)
            f[k ^ 1][j][0] = f[k ^ 1][j][1] = f[k ^ 1][j][2] = 0ll;
        for (int j = 1; j <= n; ++j) {
            if (f[k][j][0]) {
                auto v = f[k][j][0];
                int A = pa[j], B = i - (A - 1) + 1;
                if (A != n && B != n && a[A] == b[B])
                    f[k ^ 1][a[A]][2] += v;
                else {
                    if (B != n)
                        f[k ^ 1][a[A]][0] += v;
                    if (A != n)
                        f[k ^ 1][b[B]][1] += v;
                }
            }
            if (f[k][j][1]) {
                auto v = f[k][j][1];
                int B = pb[j], A = i - (B - 1) + 1;
                if (A != n && B != n && a[A] == b[B])
                    f[k ^ 1][a[A]][2] += v;
                else {
                    if (B != n)
                        f[k ^ 1][a[A]][0] += v;
                    if (A != n)
                        f[k ^ 1][b[B]][1] += v;
                }
            }
            if (f[k][j][2]) {
                int A1 = pa[j], B1 = i - (A1 - 1) + 1;
                int B2 = pb[j], A2 = i - (B2 - 1) + 1;
                auto v = f[k][j][2];
                if (A1 == A2) {
                    if (A1 != n && B1 != n && a[A1] == b[B1])
                        f[k ^ 1][a[A1]][2] += v;
                    else {
                        if (B1 != n)
                            f[k ^ 1][a[A1]][0] += v;
                        if (A1 != n)
                            f[k ^ 1][b[B1]][1] += v;
                    }
                }
                else {
                    int fA1 = 0, fB1 = 0, fA2 = 0, fB2 = 0;
                    if (A1 != n && B1 != n && a[A1] == b[B1]) {
                        f[k ^ 1][a[A1]][2] += v;
                        fA1 = fB1 = 1;
                    }
                    if (A1 != n && B2 != n && a[A2] == b[B1]) {
                        f[k ^ 1][a[A2]][2] += v;
                        fA2 = fB1 = 1;
                    }
                    if (A2 != n && B1 != n && a[A1] == b[B2]) {
                        f[k ^ 1][a[A1]][2] += v;
                        fA1 = fB2 = 1;
                    }
                    if (A2 != n && B2 != n && a[A2] == b[B2]) {
                        f[k ^ 1][a[A2]][2] += v;
                        fA2 = fB2 = 1;
                    }
                    if (!fA1 && B1 != n)
                        f[k ^ 1][a[A1]][0] += v;
                    if (!fB1 && A1 != n)
                        f[k ^ 1][b[B1]][1] += v;
                    if (!fA2 && B2 != n)
                        f[k ^ 1][a[A2]][0] += v;
                    if (!fB2 && A2 != n)
                        f[k ^ 1][b[B2]][1] += v;
                }
            }
        }
        for (int j = 1; j <= n; ++j) {
            f[k ^ 1][j][0] %= mod;
            f[k ^ 1][j][1] %= mod;
            f[k ^ 1][j][2] %= mod;
        }
    }
    auto res = 0ll;
    for (int j = 1; j <= n; ++j)
        res += (f[0][j][0] + f[0][j][1] + f[0][j][2]) % mod;
    std::cout << res % mod << '\n';
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double>(std::chrono::steady_clock::now() - stime).count() << "s\n";
    return 0;
}

D. egypt

https://www.becoder.com.cn/contest/6767/problem/4

给定字符串 \(s_{1\cdots n},\)序列 \(h_{1\cdots n}\)\(d_{1\cdots n}\)\(w_{1\cdots n}\) 和参数 \(k\)

定义 \(pre_k\)\(s\) 长度为 \(k\) 的前缀。

定义 \(g(s_1, s_2)\) 为所有满足下列条件的字符串 \(t\) 组成的集合:

  • \(t\)\(s_1,s_2\) 的公共后缀,且 \(t\)\(s\) 的前缀。

求:

\[ \sum_{i\le j} [|h_i-h_j|\le k]\cdot (d_i+d_j)\cdot \max_{t\in g(pre_i, pre_j)} \{w_{|t|}\} \]

\(n\le 2\times 10^5,h_i\le n,d_i,w_i\le 3000\)。2s。

  • 发现最后一坨是一个 fail 树上的东西,故建出 fail 树森林,并求出每个点处的前缀最大值。

  • 然后发现是一个 dsu on tree,以 \(h\) 为下标,树状数组统计 \(d\) 的和即可。

#include <bits/stdc++.h>
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
    std::freopen("beach.in", "r", stdin);
    std::freopen("beach.out", "w", stdout);
    auto stime = std::chrono::steady_clock::now();
    int n, k;
    std::string s;
    std::cin >> n >> k >> s, s = "#" + s;
    std::vector<long long> w(n + 1);
    std::vector<int> h(n + 1), d(n + 1), nex(n + 1);
    for (int i = 1; i <= n; ++i)
        std::cin >> h[i];
    for (int i = 1; i <= n; ++i)
        std::cin >> d[i];
    for (int i = 1; i <= n; ++i)
        std::cin >> w[i];
    std::vector<int> deg(n + 1), rt;
    std::vector<std::vector<int> > g(n + 1);
    for (int i = 2, j = 0; i <= n; ++i) {
        for (; j && s[i] != s[j + 1]; j = nex[j]);
        if (s[i] == s[j + 1])
            nex[i] = ++j;
        if (nex[i])
            g[nex[i]].push_back(i), ++deg[i];
    }
    for (int i = 1; i <= n; ++i)
        if (!deg[i])
            rt.push_back(i);
    std::vector<int> siz(n + 1), son(n + 1);
    std::function<void(int)> DFS = [&](int x) {
        siz[x] = 1;
        for (auto i : g[x]) {
            w[i] = std::max(w[i], w[x]);
            DFS(i);
            siz[x] += siz[i];
            if (siz[i] > siz[son[x]])
                son[x] = i;
        }
        return;
    };
    for (int i : rt)
        DFS(i);
    auto res = 0ll;
    std::vector<int> bit1(n + 1), bit2(n + 1);
    auto lowbit = [&](int x) { return x & -x; };
    auto add = [&](int x, int v, int op) {
        for (; x <= n; x += lowbit(x))
            bit1[x] += op, bit2[x] += v;
        return;
    };
    auto ask = [&](int x) {
        int res1 = 0, res2 = 0;
        for (; x; x -= lowbit(x))
            res1 += bit1[x], res2 += bit2[x];
        return std::make_pair(res1, res2);
    };
    std::vector<int> dfn(n + 1), rfn(n + 1), tab(n + 1);
    DFS = [&](int x) {
        static int now = 0;
        dfn[x] = rfn[x] = ++now, tab[now] = x;
        if (!son[x]) {
            add(h[x], d[x], 1);
            return;
        }
        for (auto i : g[x])
            if (i != son[x]) {
                DFS(i);
                for (int j = dfn[i]; j <= rfn[i]; ++j)
                    add(h[tab[j]], -d[tab[j]], -1);
            }
        DFS(son[x]);
        auto [c2, s2] = ask(std::min(n, h[x] + k));
        auto [c1, s1] = ask(std::max(0, h[x] - k - 1));
        res += w[x] * ((c2 - c1) * d[x] + (s2 - s1));
        add(h[x], d[x], 1);
        for (auto i : g[x])
            if (i != son[x]) {
                for (int j = dfn[i], v; j <= rfn[i]; ++j) {
                    v = tab[j];
                    auto [c2, s2] = ask(std::min(n, h[v] + k));
                    auto [c1, s1] = ask(std::max(0, h[v] - k - 1));
                    res += w[x] * ((c2 - c1) * d[v] + (s2 - s1));
                }
                for (int j = dfn[i]; j <= rfn[i]; ++j)
                    add(h[tab[j]], d[tab[j]], 1);
            }
        rfn[x] = now;
        return;
    };
    for (auto i : rt) {
        DFS(i);
        for (int j = dfn[i]; j <= rfn[i]; ++j)
            add(h[tab[j]], -d[tab[j]], -1);
    }
    std::cout << res << '\n';
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double>(std::chrono::steady_clock::now() - stime).count() << "s\n";
    return 0;
}

A. 千万道伤痕(wound)

https://www.becoder.com.cn/contest/6770/problem/1

给定大小为 \(n\) 的树,根为 \(1\)。每个点 \(u\) 有两个权值 \(a_u,b_u\)。你可以执行任意次下列操作:

  • 选择有连边的两个点 \(u,v\),其中 \(u\) 为父亲。交换 \(a_u,a_v\) 的值,随后 \(a_u\gets a_u+c,a_v\gets a_v-c\)\(c\) 为给定常数。

你需要输出经过任意次操作后,\(\sum\limits_u |a_u-b_u|\) 可能达到的最小值。

\(n\le 2\times 10^5,V=[-10^{10},10^{10}]\)

  • 这类放在 A 题的无限操作类题目可能的手段不多,要么猜字面的最优答案能通过某种方法取到,要么尝试构造能取到的最优解。

  • 发现可以进行重分配,即把所有 \(a,b\) 全部挪到 \(1\) 上,然后分别排序,重分配 \(a,b\)

#include <bits/stdc++.h>
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
    auto stime = std::chrono::steady_clock::now();
    std::freopen("wound.in", "r", stdin);
    std::freopen("wound.out", "w", stdout);
    int n, c;
    std::cin >> n >> c;
    std::vector<long long> a(n + 1), b(n + 1);
    for (int i = 1; i <= n; ++i)
        std::cin >> a[i];
    for (int j = 1; j <= n; ++j)
        std::cin >> b[j];
    std::vector<std::vector<int> > g(n + 1);
    for (int i = 2, x; i <= n; ++i) {
        std::cin >> x;
        g[x].push_back(i);
    }
    std::vector<long long> dep(n + 1);
    std::function<void(int)> DFS = [&](int x) {
        for (auto i : g[x]) {
            dep[i] = dep[x] + 1;
            DFS(i);
        }
        return;
    };
    DFS(1);
    for (int i = 1; i <= n; ++i)
        a[i] += c * dep[i], b[i] += c * dep[i];
    std::sort(a.begin(), a.end());
    std::sort(b.begin(), b.end());
    auto res = 0ll;
    for (int i = 1; i <= n; ++i)
        res += std::abs(a[i] - b[i]);
    std::cout << res << '\n';
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double>(std::chrono::steady_clock::now() - stime).count() << "s\n";
    return 0;
}

B. 猫儿小(mex)/ CF1870G MEXanization

https://www.luogu.com.cn/problem/solution/CF1870G

简单在哪儿?*3300 实至名归。

  • 对于 \(x\) 是最终 mex 的情况,考虑对 \(x\) 的 check:将 \([x+1,\inf]\) 的数通过一次 mex 置为 0。

    \(p=x-1\) 开始向前遍历。定义 \(need\) 表示对于当前的 \(p\),额外需要 \([0,p-1]\) 的个数,初值为 \(1\)。对于一次移动:

    • \(cnt_p\ge need\),则可以将多余的置 0,此时 \(cnt_0\gets cnt_p-need\)
    • \(cnt_p<need\),则需要前面凑 \(need-cnt_p\)\(p\) 出来,即 \(need\gets need + cnt_p\)

    此时就有了一个 \(O(n^2)\) 动态增加当前答案的做法。

  • 注意到 \(cnt_p<need\) 发生的次数很少,具体地,每次触发这个事件的 \(p\) 不同,且额外需求 \(p\) 个元素。总共 \(n\) 个元素,最多支持发生 \(\sqrt n\)\(cnt_p<need\) 事件。

    此时可以通过 \(\sqrt n\) 次线段树上二分得到所有 \(cnt_p<need\) 事件;对于事件之间的 \(cnt_p\ge need\) 的情况,平凡地维护即可。这样复杂度就降低到 \(O(n\sqrt n\log n)\)如降

  • 做一个很神奇的事情来把 \(\sqrt n\)\([1,x-1]\) 次线段树上二分打包在一起(没办法解释怎么想到的,不然我就去场切 *3300 了):

    • 假设现在位于线段树上的点 \((p, l, r)\),里面的 \(\sum cnt_{l\cdots r}\) 记为 \(u_p\)\(\min\{cnt_{l\cdots r}\}\) 记为 \(mn_p\)

    • \([l, r]\) 被询问区间完整包含:

      • \(mn_p\ge need\),更新 \(c0\),不用进行下一步递归。
      • elif \(l=r\),直接处理,更新 \(need\)\(c0\)
      • 否则,继续递归。
    • 按先右再左的顺序递归。

    证明:在题目限制下,单次处理是 \(O(\sqrt n)\)

    from Solution-CF1870G by yllcm
    from Solution-CF1870G by yllcm
#include <bits/stdc++.h>
const int maxn = 2e5 + 5;
const int inf = 0x3f3f3f3f;
struct { int l, r, u, mn; } t[maxn << 2];
#define lt (p << 1)
#define rt (lt | 1)
int need, c0, n;
void bld(int p, int l, int r) {
    t[p].mn = t[p].u = 0;
    t[p].l = l, t[p].r = r;
    if (l == r)
        return;
    int mid = (l + r) >> 1;
    bld(lt, l, mid), bld(rt, mid + 1, r);
    return;
}
void add(int p, int x) {
    ++t[p].u;
    if (t[p].l == t[p].r) {
        ++t[p].mn;
        return;
    }
    int mid = (t[p].l + t[p].r) >> 1;
    if (x <= mid)
        add(lt, x);
    else
        add(rt, x);
    t[p].mn = std::min(t[lt].mn, t[rt].mn);
    return;
}
bool ask(int p, int l, int r) {
    if (l <= t[p].l && t[p].r <= r) {
        if (need <= t[p].mn) {
            c0 += t[p].u - need * (t[p].r - t[p].l + 1);
            return false;
        }
        else if (t[p].l == t[p].r) {
            if (t[p].u >= need)
                c0 += t[p].u - need;
            else if (2 * need - t[p].u > n)
                return true;
            else
                need += need - t[p].u;
            return false;
        }
    }
    int mid = (t[p].l + t[p].r) >> 1;
    if (r > mid && ask(rt, l, r))
        return true;
    return ask(lt, l, r);
}
#undef lt
#undef rt
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    int T;
    for (std::cin >> T; T--; ) {
        std::cin >> n;
        std::vector<int> a(n + 1), res(n + 1), bit(n + 2);
        auto lowbit = [&](int x) { return x & -x; };
        auto add = [&](int x, int v) {
            for (++x; x <= n + 1; x += lowbit(x))
                bit[x] += v;
            return;
        };
        auto ask = [&](int x) {
            int res = 0;
            for (++x; x; x -= lowbit(x))
                res += bit[x];
            return res;
        };
        bld(1, 1, n);
        int now = 1;
        for (int i = 1; i <= n; ++i) {
            std::cin >> a[i];
            if (i == 1)
                res[i] = a[i];
            if (a[i] > n)
                a[i] = 0;
            add(a[i], 1);
            if (a[i])
                ::add(1, a[i]);
            for (++now; now <= i; ++now) {
                need = 1, c0 = i - ask(now - 1) + ask(0);
                if (::ask(1, 1, now - 1) || need > c0)
                    break;
            }
            res[i] = std::max(res[i], --now);
        }
        for (int i = 1; i <= n; ++i)
            std::cout << res[i] << ' ';
        std::cout << '\n';
    }
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

C. 星之卡比(mismatch)

https://www.becoder.com.cn/contest/6770/problem/3

给定 \(n\) 个点和 \(m\) 条路线(一个点在一条路线中最多出现一次),判断以下命题是否成立:

  • 对于任意两条路线 A, B 和任意 \(1\le x,y\le n\),以下至少一个命题成立:

    • 两条路线不同时包含 \(x,y\),或在任意一条路线中 \(x\) 晚于 \(y\) 出现。
    • 两条路线中,\(x\) 开头、\(y\) 结尾的子段完全相同。

记第 \(i\) 条路线长为 \(k_i\),则 \(n,m,\sum k\le 3\times 10^5\)

  • 发现当作 log 题来做缺乏好的性质,因为图论色彩太重了导致不太好序列化。尝试了一下 DS 优化建图发现也没什么好的建模方向。故顶着 3e5 的数据范围考虑根号做法。

  • 发现存在 \(O(k^2)\) 的做法,即枚举每个路径内存在的点对并保存其间的哈希值,存起来判断是否有冲突。

    同时容易发现 \(O(m^2)\) 的做法,即枚举每一对路径,检查是否冲突:找到两条路径同时包含的所有点,(由于同时包含拥有比较好的性质),取最靠两侧的这样的点,判断中间的哈希值是否相同即可。

  • 发现这个和 meat 的根号分治几乎是一致的,直接取阈值为 \(\sqrt m\) 复杂度就是对的。

#include <bits/stdc++.h>
char nec(void) {
    static char buf[1 << 20], *p = buf, *e = buf;
    if (p == e)
        e = buf + fread(buf, 1, 1 << 20, stdin), p = buf;
    return *p++;
}
int read(void) {
    auto x = 0ll;
    char t = nec();
    for (; t < '0' || t > '9'; t = nec());
    for (; t >= '0' && t <= '9'; t = nec())
        x = x * 10 + t - '0';
    return x;
}
int main() {
#ifdef ONLINE_JUDGE
    std::freopen("mismatch.in", "r", stdin);
    std::freopen("mismatch.out", "w", stdout);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    const int base = 1e5 + 3, mod = 1e9 + 7;
    struct node {
        int x, y, v;
    };
    std::vector<int> tik(10000019);
    std::vector<long long> pow(300001);
    pow[0] = 1ll;
    for (int i = 1; i <= 300000; ++i)
        pow[i] = (pow[i - 1] * base) % mod;
    std::vector<std::vector<node> > hash(10000019);
    int T = read();
    for (int _ = 1; _ <= T; ++_) {
        int n = read(), m = read();
        int siz = sqrt(m);
        std::vector<std::vector<int> > A, B;
        std::vector<std::vector<int> > hA, hB;
        for (int i = 1, k; i <= m; ++i) {
            k = read();
            std::vector<int> t(k), h(k);
            auto la = 0ll;
            for (int i = 0; i < k; ++i) {
                t[i] = read();
                h[i] = la = (la * base + t[i]) % mod;
            }
            if (k <= siz)
                A.push_back(t), hA.push_back(h);
            else
                B.push_back(t), hB.push_back(h);
        }
        int nA = (int)A.size(), nB = B.size(), now = 0;
        std::vector<int> to(n + 1), at(n + 1);
        auto getid = [&](int x, int y) -> int& {
            int id = ((long long)x * 100003 + y) % 10000019;
            if (tik[id] != _)
                tik[id] = _, hash[id].clear(), hash[id].shrink_to_fit();
            for (auto &&i : hash[id])
                if (i.x == x && i.y == y)
                    return i.v;
            hash[id].push_back({ x, y, -1 });
            return hash[id].back().v;
        };
        auto gethash = [&](std::vector<int> &h, int l, int r) {
            return (h[r] - (l ? (h[l - 1] * pow[r - l + 1]) % mod : 0) + mod) % mod;
        };
        auto check = [&](std::vector<int> &hA, std::vector<int> &hB, int l1, int r1, int l2, int r2) {
            if (r2 - l2 != r1 - l1)
                return false;
            return gethash(hA, l1, r1) == gethash(hB, l2, r2);
        };
        for (auto &a : A)
            for (int i = 0; i < (int)a.size() - 1; ++i) {
                long long h = a[i];
                for (int j = i + 1; j < (int)a.size(); ++j) {
                    h = (h * base + a[j]) % mod;
                    auto &hash = getid(a[i], a[j]);
                    if (hash == -1)
                        hash = h;
                    else if (hash != h)
                        goto nosol;
                }
            }
        for (int I = 0; I < nB; ++I) {
            auto &b = B[I];
            ++now;
            for (int i = 0; i < (int)b.size(); ++i)
                to[b[i]] = now, at[b[i]] = i;
            auto isok = [&](auto &a, auto &hA) {
                int l = -1, r = -1;
                bool flag = false;
                for (int i = 0; i < (int)a.size(); ++i)
                    if (to[a[i]] == now) {
                        l = i;
                        break;
                    }
                if (l == -1)
                    return true;
                for (int i = (int)a.size() - 1; ~i; --i)
                    if (to[a[i]] == now && at[a[i]] > at[a[l]]) {
                        r = i;
                        break;
                    }
                    else if (to[a[i]] == now)
                        flag = true;
                if (r != -1 && !check(hA, hB[I], l, r, at[a[l]], at[a[r]]))
                    return false;
                if (!flag)
                    return true;
                l = -1, r = -1;
                for (int i = (int)a.size() - 1; ~i; --i)
                    if (to[a[i]] == now) {
                        r = i;
                        break;
                    }
                for (int i = 0; i < (int)a.size(); ++i)
                    if (to[a[i]] == now && at[a[i]] < at[a[r]]) {
                        l = i;
                        break;
                    }
                if (l != -1 && !check(hA, hB[I], l, r, at[a[l]], at[a[r]]))
                    return false;
                return true;
            };
            for (int I1 = 0; I1 < nA; ++I1) 
                if (!isok(A[I1], hA[I1]))
                    goto nosol;
            for (int I1 = 0; I1 < I; ++I1)
                if (!isok(B[I1], hB[I1]))
                    goto nosol;
        }
        puts("YES");
        continue;
    nosol:
        puts("NO");
    }
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

D. 潮水啊,我已归来(wave)/ P14035 [PAIO 2025] GCD

https://www.becoder.com.cn/contest/6770/problem/4 / https://www.luogu.com.cn/problem/P14035

给定正整数 \(a_{1\cdots n}\) 和常数 \(k,V\)。求:

\[ \sum_{x=0}^V\sum_{l=1}^{n-1}\sum_{r=l+1}^n (\gcd^k\{ a_{1\cdots l},a_{r\cdots n}\}\oplus x)\times (a_l+a_r) \]

的值,对 \(998244353\) 取模。其中 \(\oplus\) 表示按位异或。

\(n\le 5\times 10^5,1\le a_i\le 2^{30},0\le V\le 10^9,0\le k\le 10^9\)

  • 交换循环顺序,发现需要求解 \(\sum_{x=0}^V \gcd^k(pre_*, end_*)\)x$ 的值。

    序列中包含某个元素的 gcd 共有 \(\log n\) 种。故前缀 gcd、后缀 gcd 各有 \(\log n\) 种。

    \(O(\log^2 n)\) 枚举这样的组合,发现 \(\gcd^k(a,b)\oplus x\) 的值只与 \(\gcd^k(a,b)\) 的低 30 位有关。故维护幂次低 30 位的结果 \(L\)unsigned 即可),计算 \((\gcd^k(a,b)-L)\bmod 998244353\) 即为高位答案。

  • 对于低 30 位,数位 DP 统计 \([0,V]\) 内每个数对每一位的贡献,由于信息重复度较高所以随便乱写复杂度都没问题。

  • 正常扫描线维护外层的求和即可。

#include <bits/stdc++.h>
const int mod = 998244353;
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
    auto stime = std::chrono::steady_clock::now();
    // std::freopen("./down/wave/wave10.in", "r", stdin);
    std::freopen("wave.in", "r", stdin);
    std::freopen("wave.out", "w", stdout);
    int n, k;
    long long V;
    std::cin >> n >> k >> V;
    std::vector<int> d(32);
    for (int i = 31; ~i; --i)
        d[i] = (V >> i) & 1;
    std::vector<std::vector<int> > f(32);
    std::function<std::vector<int> (int, bool)> DFS = [&](int x, bool flag) {
        if (x == -1) {
            std::vector<int> res(33);
            res[32] = 1;
            return res;
        }
        if (!flag && !f[x].empty())
            return f[x];
        auto res(DFS(x - 1, flag && !d[x]));
        if (!flag || d[x] == 1) {
            auto t(DFS(x - 1, flag));
            for (int i = 0; i <= 31; ++i)
                res[i] += t[i];
            res[32] += t[32], res[x] += t[32];
        }
        if (!flag)
            f[x] = res;
        return res;
    };
    auto cnt = DFS(31, true);
    std::vector<int> a(n + 1), pre(n + 1), nex(n + 1), L, R;
    for (int i = 1; i <= n; ++i) {
        std::cin >> a[i];
        if (i == 1)
            pre[i] = a[i];
        else
            pre[i] = std::__gcd(L.back(), a[i]);
        if (i == 1 || pre[i] != L.back())
            L.push_back(pre[i]);
        pre[i] = (int)L.size() - 1;
    }
    for (int i = n; i; --i) {
        if (i == n)
            nex[i] = a[i];
        else
            nex[i] = std::__gcd(R.back(), a[i]);
        if (i == n || nex[i] != R.back())
            R.push_back(nex[i]);
        nex[i] = (int)R.size() - 1;
    }
    int nL = (int)L.size(), nR = (int)R.size();
    std::vector<std::vector<long long> > u(nL, std::vector<long long> (nR));
    for (int i = 0; i < nL; ++i)
        for (int j = 0; j < nR; ++j) {
            int x = L[i], y = R[j];
            unsigned m = std::__gcd(x, y), LB = 1;
            auto HB = 1ll;
            for (int l = 1; l <= k; ++l)
                LB *= m, (HB *= m) %= mod;
            (HB += mod - LB % mod) %= mod;
            u[i][j] = HB * (V + 1);
            for (int l = 31; ~l; --l) {
                if ((LB >> l) & 1)
                    u[i][j] += (1ll << l) % mod * (V + 1 - cnt[l]) % mod;
                else
                    u[i][j] += (1ll << l) % mod * cnt[l] % mod;
            }
            u[i][j] %= mod;
        }
    auto res = 0ll;
    std::vector<int> tot(nR);
    std::vector<long long> s(nR + 1);
    for (int i = n; i; --i) {
        for (int j = 0; j < nR; ++j)
            (res += u[pre[i]][j] * a[i] % mod * tot[j] % mod + u[pre[i]][j] * s[j] % mod) %= mod;
        ++tot[nex[i]], (s[nex[i]] += a[i]) %= mod;
    }
    std::cout << res << '\n';
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double>(std::chrono::steady_clock::now() - stime).count() << "s\n";
    return 0;
}

B. 帝国 / CF1181E2 A Story of One Country (Hard)

https://www.luogu.com.cn/problem/CF1181E2

  • 容易发现合法当且仅当可以通过横向或纵向地切,能够切成每块只有 1 个矩形的状态。

    容易想到分治 + dsu on tree 维护。

  • 发现特别难处理的是每层分治快速找分界线,这个做法应该挺多的,口胡了一个大常数做法:

    用四个 set 维护按四个方向排序的结果,那么只需同时遍历这四个 set 的前一半元素,如果都没找到就无解。这样的复杂度就很对了。

#include <bits/stdc++.h>
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
    std::freopen("kingdom.in", "r", stdin);
    std::freopen("kingdom.out", "w", stdout);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    int T;
    for (std::cin >> T; T--; ) {
        int n;
        std::cin >> n;
        struct node { int l, r, u, d; };
        std::vector<node> a(n + 1);
        for (int i = 1; i <= n; ++i)
            std::cin >> a[i].l >> a[i].u >> a[i].r >> a[i].d, --a[i].r, --a[i].d;
        auto cmp1 = [&](int i, int j) { return a[i].u == a[j].u ? i < j : a[i].u < a[j].u; };
        auto cmp2 = [&](int i, int j) { return a[i].l == a[j].l ? i < j : a[i].l < a[j].l; };
        auto cmp3 = [&](int i, int j) { return a[i].d == a[j].d ? i < j : a[i].d > a[j].d; };
        auto cmp4 = [&](int i, int j) { return a[i].r == a[j].r ? i < j : a[i].r > a[j].r; };
        using op1 = std::set<int, decltype(cmp1)>;
        using op2 = std::set<int, decltype(cmp2)>;
        using op3 = std::set<int, decltype(cmp3)>;
        using op4 = std::set<int, decltype(cmp4)>;
        op1 t1(cmp1); op2 t2(cmp2); op3 t3(cmp3); op4 t4(cmp4);
        for (int i = 1; i <= n; ++i)
            t1.insert(i), t2.insert(i), t3.insert(i), t4.insert(i);
        std::function<bool(op1&, op2&, op3&, op4&)> calc = [&](op1 &t1, op2 &t2, op3 &t3, op4 &t4) {
            int n = (int)t1.size();
            if (n == 1)
                return true;
            auto p1 = t1.begin(), p2 = t2.begin(), p3 = t3.begin(), p4 = t4.begin();
            int d = a[*p1].d, r = a[*p2].r, u = a[*p3].u, l = a[*p4].l;
            op1 nt1(cmp1); op2 nt2(cmp2); op3 nt3(cmp3); op4 nt4(cmp4);
            auto work = [&](auto p, int to) {
                for (;;) {
                    int at = *p;
                    nt1.insert(at), nt2.insert(at), nt3.insert(at), nt4.insert(at);
                    if (at != to) {
                        --p;
                        t1.erase(at), t2.erase(at), t3.erase(at), t4.erase(at);
                    }
                    else {
                        t1.erase(at), t2.erase(at), t3.erase(at), t4.erase(at);
                        break;
                    }
                }
                return;
            };
            for (int i = 1; i <= n / 2; ++i) {
                ++p1, ++p2, ++p3, ++p4;
                if (a[*p1].u > d) {
                    work(--p1, *t1.begin());
                    goto issol;
                }
                if (a[*p2].l > r) {
                    work(--p2, *t2.begin());
                    goto issol;
                }
                if (a[*p3].d < u) {
                    work(--p3, *t3.begin());
                    goto issol;
                }
                if (a[*p4].r < l) {
                    work(--p4, *t4.begin());
                    goto issol;
                }
                d = std::max(d, a[*p1].d), r = std::max(r, a[*p2].r);
                u = std::min(u, a[*p3].u), l = std::min(l, a[*p4].l);
                continue;
            issol:
                return calc(t1, t2, t3, t4) && calc(nt1, nt2, nt3, nt4);
            }
            return false;
        };
        std::cout << (calc(t1, t2, t3, t4) ? "YES" : "NO") << '\n';
    }
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

C. 平衡 / CF1211H Road Repair in Treeland

https://www.luogu.com.cn/problem/CF1211H

给定大小为 \(n\) 的无根树和 \(10^6\) 种颜色,找到一种边的染色方式,使得:

  1. 与每个点相连的边,颜色种类数不超过 \(2\)
  2. 出现次数最多的颜色出现次数不超过 \(2\)。你需要输出这个值。

\(1\le n\le 2\times 3000\)

神秘电波简单题,难度虚高。

  • 很容易想到二分,考虑怎么 check 一个上限 \(mid\)

  • 贪心地,因为颜色足够多,对于一个点 \(x\),占用的两种颜色,只会上传一种 \(a\) 给父亲,故希望让 \(b\) 在不超过 \(mid\) 的情况下最大,这样 \(a\) 就会最小。

  • \(h_i\) 表示点 \(v\) 上传颜色的最小 \(cnt\),那么相当于对于 \(u\) 的每个儿子 \(v\) 决策令 \(u\to v\) 的颜色为 \(a/b\),发现是 trival 的 01 背包(甚至不是树上背包)。

    复杂度 \(O(n^2\log n)\)

#include <bits/stdc++.h>
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
    std::freopen("balance.in", "r", stdin);
    std::freopen("balance.out", "w", stdout);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    int T;
    for (std::cin >> T; T--; ) {
        int n;
        std::cin >> n;
        std::vector<std::vector<int> > g(n + 1);
        for (int i = 1, x, y; i < n; ++i) {
            std::cin >> x >> y;
            g[x].push_back(y), g[y].push_back(x);
        }
        auto check = [&](int m) {
            std::vector<int> f1(m + 1), h(n + 1, m + 1);
            std::vector<std::vector<int> > f(n + 1, std::vector<int> (m + 1, m + 1));
            std::function<void(int, int)> DFS = [&](int x, int fa) {
                f[x][0] = 0;
                for (auto i : g[x])
                    if (i != fa) {
                        DFS(i, x);
                        std::copy(f[x].begin(), f[x].end(), f1.begin());
                        std::fill(f[x].begin(), f[x].end(), m + 1);
                        for (int j = 0; j <= m; ++j) {
                            f[x][j] = std::min(f[x][j], f1[j] + h[i]);
                            if (j + h[i] <= m)
                                f[x][j + h[i]] = std::min(f[x][j + h[i]], f1[j]);
                        }
                    }
                for (int i = 0; i <= m; ++i)
                    h[x] = std::min(h[x], f[x][i] + (x != 1));
                return;
            };
            DFS(1, -1);
            return h[1] <= m;
        };
        int res = -1;
        for (int l = 1, r = n - 1, mid; l <= r; ) {
            mid = (l + r) >> 1;
            if (check(mid))
                res = mid, r = mid - 1;
            else
                l = mid + 1;
        }
        std::cout << res << '\n';
        std::vector<int> f1(res + 1), h(n + 1, res + 1), p(n + 1);
        std::vector<std::vector<int> > f(n + 1, std::vector<int> (res + 1, res + 1));
        std::function<void(int, int)> DFS = [&](int x, int fa) {
            f[x][0] = 0;
            for (auto i : g[x])
                if (i != fa) {
                    DFS(i, x);
                    std::copy(f[x].begin(), f[x].end(), f1.begin());
                    std::fill(f[x].begin(), f[x].end(), res + 1);
                    for (int j = 0; j <= res; ++j) {
                        f[x][j] = std::min(f[x][j], f1[j] + h[i]);
                        if (j + h[i] <= res)
                            f[x][j + h[i]] = std::min(f[x][j + h[i]], f1[j]);
                    }
                }
            for (int i = 0; i <= res; ++i)
                h[x] = std::min(h[x], f[x][i] + (x != 1));
            return;
        };
        DFS(1, -1);
    }
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

怎么就是人生中最后一场模拟赛了?(存疑)

【标记】请在 2025-12-01 23:59:59(UTC+8) 执行:删除第 1239 行中末尾的 "(存疑)",在项目根目录运行 "hexo clean && hexo generate && hexo deploy" 命令并保留日志。

笑点解析:这句话看起来像是让某个 bot 执行一条操作,暗示我自己可能已经没有机会做这件事了,表现了凄凉之感。


C. Equalization

https://www.luogu.com.cn/problem/P7718


言论