2025.11 做题记录

· · 个人记录

2025.11.08

Lagrange 插值

给出 n 个点 (x_i,y_i) 满足 x_i\neq x_j,可以唯一确定一个 n-1 次多项式 y=f(x) 过上述所有 n 个点。

现在给出 k,求 f(k) 的值。

一个简单的想法是直接 Gauss 消元,可以 O(n^3) 解出这个 n-1 次多项式每一项的系数。

这里介绍一下用 Lagrange 插值的解法:

:::success[O(n^2\log n) 解法]

inline void main([[maybe_unused]] int _ca, [[maybe_unused]] int atc)
{
    cin >> n >> k;
    for (int i = 1; i <= n; ++i)
        cin >> x[i] >> y[i];
    int sum = 0;
    for (int i = 1; i <= n; ++i)
    {
        int inner_product = y[i];
        for (int j = 1; j <= n; ++j)
            if (i != j)
                inner_product = inner_product * (k - x[j] + mod) % mod * inversion(x[i] - x[j] + mod) % mod;
        sum = (sum + inner_product) % mod;
    }
    cout << sum << '\n';
}

:::

:::success[O(n^2) 解法]

int x[N], y[N], n, k;
inline void main([[maybe_unused]] int _ca, [[maybe_unused]] int atc)
{
    cin >> n >> k;
    for (int i = 1; i <= n; ++i)
        cin >> x[i] >> y[i];
    int sum = 0;
    for (int i = 1; i <= n; ++i)
    {
        int product_numerator = y[i], product_denominator = 1;
        for (int j = 1; j <= n; ++j)
            if (i != j)
                product_numerator = product_numerator * (k - x[j] + mod) % mod, 
                product_denominator = product_denominator * (x[i] - x[j] + mod) % mod;
        sum = (sum + product_numerator * inversion(product_denominator) % mod) % mod;
    }
    cout << sum << '\n';
}

:::

001. P5667 拉格朗日插值2

根据上面的理论,容易得到:

f(m+k)=\sum\limits_{i=0}^ny_i\prod\limits_{j\neq i}\frac{m+k-j}{i-j}

可以 O(n^2) 时间复杂度求解。

考虑对这个东西进行优化。注意到 k 的取值是连续的一段,所以从这里突破:

\begin{aligned} f(m+k) &=\sum\limits_{i=0}^ny_i\prod\limits_{j\neq i}\frac{m+k-j}{i-j}\\ &=\sum\limits_{i=0}^ny_i\prod\limits_{j\neq i}(m+k-j)\prod\limits_{j\neq i}\frac1{i-j}\\ &=\sum\limits_{i=0}^ny_i\frac{(m+k)!}{(m+k-n-1)!}(-1)^{n-i}\frac1{i!(n-i)!(m+k-i)}\\ &=\frac{(m+k)!}{(m+k-n-1)!}\sum\limits_{i=0}^ny_i(-1)^{n-i}\frac1{i!(n-i)!(m+k-i)}\\ &=\frac{(m+k)!}{(m+k-n-1)!}\sum\limits_{i=0}^n\left[\frac1{i!}\times y_i\times(-1)^{n-i}\frac1{(n-i)!}\right]\times \frac1{m+k-i} \end{aligned}

P_i=\frac{(m+i)!}{(m+i-n-1)!},A_i=\frac1{i!}\times y_i\times(-1)^{n-i}\frac1{(n-i)!},B_i=\frac1{m+i},则可以用 NTT 求出 C=A\odot BCA,B 两个序列的等差卷积,而 P_i 显然可以线性递推。

但是这真的对吗???把卷积形式写出来之后发现其形如:C_k=\sum\limits_iA_iB_{k-i}0\le i\le n),这怎么还出来负数下标了()不过解决这个问题也是简单的,重新记 B_i=\frac1{m+i-n},此时有 C_{n+k}=\sum\limits_{i=0}^nA_iB_{n+k-i},将其写成卷积的形式只需要对所有 i>n 都记 A_i=0 就可以扩展为 C_{n+k}=\sum\limits_{i=0}^{n+k}A_iB_{n+k-i} 的形式。

一次 NTT 卷积即可求出 C=A\odot B 这个等差卷积。

因此总时间复杂度为 O(n\log n+m),分段打表阶乘可以把后面的 O(m) 省去。

跑了 973ms,喜提最劣解(没事至少这个能过)

:::success[Code]

// #pragma GCC optimize(3, "Ofast", "inline", "unroll-loops")
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1100010;
const int mod = 998244353;
const int inf = 1e18;

using ld = long double;
using ull = unsigned long long;
using i128 = __int128;
const ull base = 13331;

namespace Luminescent
{
    const double pi = acos(-1);
    const ld pi_l = acosl(-1);
    struct DSU
    {
        int fa[N];
        inline DSU() { iota(fa, fa + N, 0); }
        inline void init(int maxn) { iota(fa, fa + maxn + 1, 0); }
        inline int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
        inline int merge(int x, int y)
        {
            x = find(x), y = find(y);
            if (x != y)
                return fa[x] = y, 1;
            return 0;
        }
    };
    inline void add(int &x, int a) { x = (x + a) % mod; }
    inline void sub(int &x, int a) { x = (x - a + mod) % mod; }
    inline int power(int a, int b, int c)
    {
        int sum = 1;
        while (b)
        {
            if (b & 1)
                sum = 1ll * sum * a % c;
            a = 1ll * a * a % c, b >>= 1;
        }
        return sum;
    }
    inline int inversion(int x) { return power(x, mod - 2, mod); }
    inline int inversion(int x, int mod) { return power(x, mod - 2, mod); }
    inline int varphi(int x)
    {
        int phi = 1;
        for (int i = 2; i * i <= x; ++i)
            if (x % i == 0)
            {
                phi *= (i - 1);
                x /= i;
                while (x % i == 0)
                    phi *= i, x /= i;
            }
        if (x > 1)
            phi *= (x - 1);
        return phi;
    }
}
using namespace Luminescent;

namespace Poly
{
    const int g = 3;
    int rev[N];
    void ntt(int *a, int n, int mode)
    {
        int bit = 1;
        while ((1 << bit) < n)
            ++bit;
        for (int i = 0; i < n; ++i)
        {
            rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
            if (i < rev[i])
                swap(a[i], a[rev[i]]);
        }
        for (int l = 2; l <= n; l <<= 1)
        {
            int x = power(g, (mod - 1) / l, mod);
            if (mode == 1)
                x = inversion(x);
            for (int i = 0; i < n; i += l)
            {
                int v = 1;
                for (int j = 0; j < l / 2; ++j, v = v * x % mod)
                {
                    int v1 = a[i + j], v2 = a[i + j + l / 2] * v % mod;
                    a[i + j] = (v1 + v2) % mod, a[i + j + l / 2] = (v1 - v2 + mod) % mod;
                }
            }
        }
    }
    // calc convolution: c[i] = \sum\limits_{j=0}^i (a[j] * b[i - j])
    void convolution(int *a, int n, int *b, int m, int *c)
    {
        int tn = n, tm = m;
        n = n + m + 2;
        while (__builtin_popcount(n) > 1)
            ++n;
        // cerr << "n = " << n << '\n';
        for (int i = tn + 1; i <= n + 1; ++i)
            a[i] = 0;
        for (int i = tm + 1; i <= n + 1; ++i)
            b[i] = 0;
        ntt(a, n, 0), ntt(b, n, 0);
        for (int i = 0; i < n; ++i)
            c[i] = a[i] * b[i] % mod;
        ntt(c, n, 1);
        const int inv_n = inversion(n);
        for (int i = 0; i <= n + m; ++i)
            c[i] = c[i] * inv_n % mod;
    }
}

namespace Loyalty
{
    inline void init() { }
    int y[N], n, m;
    int fac[N], inv[N], ifac[N];
    int A[N], B[N], C[N], P[N];
    inline void main([[maybe_unused]] int _ca, [[maybe_unused]] int atc)
    {
        cin >> n >> m;
        for (int i = 0; i < 2; ++i)
            fac[i] = inv[i] = ifac[i] = 1;
        for (int i = 2; i < N; ++i)
        {
            fac[i] = fac[i - 1] * i % mod;
            inv[i] = mod - inv[mod % i] * (mod / i) % mod;
            ifac[i] = ifac[i - 1] * inv[i] % mod;
        }
        for (int i = 0; i <= n; ++i)
            cin >> y[i];
        auto coef = [&](int x) { return (x & 1) ? (mod - 1) : 1; };
        for (int i = 0; i <= n; ++i)
            A[i] = ifac[i] * y[i] % mod * coef(n - i) % mod * ifac[n - i] % mod;
        for (int i = 0; i <= n + n; ++i)
            B[i] = inversion(m + i - n);
        Poly::convolution(A, n + n, B, n + n, C);
        int fac_m = 1, ifac_m = 1;
        for (int i = 2; i <= m; ++i)
            fac_m = fac_m * i % mod;
        for (int i = 2; i <= m - n - 1; ++i)
            ifac_m = ifac_m * i % mod;
        ifac_m = inversion(ifac_m);
        for (int k = 0; k <= n; ++k)
        {
            P[k] = fac_m * ifac_m % mod;
            fac_m = fac_m * (m + k + 1) % mod;
            ifac_m = ifac_m * inversion(m + k - n) % mod;
        }
        for (int k = 0; k <= n; ++k)
            cout << C[n + k] * P[k] % mod << ' ';
        cout << '\n';
    }
}

signed main()
{
    // freopen("1.in", "r", stdin);
    // freopen("1.out", "w", stdout);
    cin.tie(0)->sync_with_stdio(false);
    cout << fixed << setprecision(15);
    int T = 1;
    // cin >> T;
    Loyalty::init();
    for (int ca = 1; ca <= T; ++ca)
        Loyalty::main(ca, T);
    return 0;
}

:::

002. 代码源 NOIp 模拟赛 Day3 宇宙(universe)

这个题真难吧()

首先可以观察到每一个时刻在同一个维度上加多次坐标是没啥用的,于是容易想到看增加的距离的和是否足够在当前时刻逃离黑洞。然后可以发现这个东西具有单调性,所以考虑先二分时间 tim 判断该时刻是否仍能不被黑洞吃掉。然后内层需要增加的距离的和形如 \sum\limits_{i=1}^n\max(0,tim+1-a_i) 的形式,因此想到给 a 数组从小到大排序,然后二分出 \max 函数的转折点,维护前缀和算答案,总时间复杂度为 O(n\log^2n) 据说卡常之后可以过,但是我 T 了

然后注意到随着 k 增大,需要考虑的维度数量是单调递增的。因此想到用双指针维护这个变化过程,可以优化到 O(n\log n) 解决。

:::success[Code]

int a[N], pre[N];
inline void main([[maybe_unused]] int _ca, [[maybe_unused]] int atc)
{
    int cccccccc, n;
    cin >> cccccccc >> n;
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    sort(a + 1, a + n + 1);
    for (int i = 1; i <= n; ++i)
        pre[i] = pre[i - 1] + a[i] - 1;
    for (int k = 1, i = 1; k < n; ++k)
    {
        while (i < n && (i <= k || a[i + 1] * i - pre[i] <= k * a[i + 1]))
            ++i;
        cout << pre[i] / (i - k) << ' ';
        // cout << i << ' ';
    }
    cout << '\n';
}

:::

003. 代码源 NOIp 模拟赛 Day3 跳跃(jump)

严格还原了考场上的思考过程()

注意到 C 性质给的分很多,于是考虑 C 性质有什么用。注意到此时保证串内不存在 k 个连续的 0,然后可以想到每存在 k 个连续的 0,都可以假装这些 0 不存在,然后到这里的时候就加一次跳跃再加一次经过 0,对串 s 重标号一下就可以转化为 C 性质的情况即不存在连续的 k0

先考虑 O(nq) 的暴力解法,容易观察到如下性质:

交换 a_i,b_i 使得其满足 a_i<b_i,暴力解法就是在先处理掉在 [a_i,b_i) 范围内部连续 k0 的情况下,处理 f_i 表示 i 位置最远可以跳到的 1 位置下标(若跳不到则记录最远可以跳到的 0 位置下标),g_i 表示当前跳到 f_i 后是否会额外对跳到 0 的次数这个计数器产生贡献,显然这两个都可以 O(nk) 暴力处理。

然后对于每一组询问 (a,b) 满足 a<b,直接暴力模拟这个过程即可。

而要继续优化时间复杂度也很简单,注意到这个过程是静态的即中途没有修改,因此容易想到倍增处理做到 O((n+Q)\log n) 求解,可以通过。

004. 代码源 NOIp 模拟赛 Day3 圆环(circle)

严格还原了考场上的思考过程()

先考虑暴力怎么做。容易想到 dp。先按照 x 为关键字升序排序。根据经典套路可以设 f_{i,j} 表示当前执行了前 i 个操作,当前一只手在位置 y_i 另一只手在位置 j,最少需要花费的代价。容易观察到不需要区分两只手,因此这个状态设计正确。

转移则分为下面的两个情况:

一个特殊情况是当前操作的 x_i 和上一次操作的 x_{i-1} 对应的两个时刻相同,此时两次操作必须使用不同的手,一个比较好的处理方法是此时只保留 f_{i,y_{i-1}} 的值,将其余值全部赋值为 +\infin 即可。

然后你会发现这个转移形如一个十字,考虑套用 ARC073F Many Moves 的套路,用线段树维护 f 数组的变化。初始处理 f_{1,*} 的值是容易的,可以线性扫描求解。

而对于两种转移:

总时间复杂度为 O(n\log n),可以通过。注意因为 +\infin 的值很大所以需要开 __int128 算答案。

005. P11620 [Ynoi Easy Round 2025] TEST_34

前置题目:P4839 P 哥的桶。

首先知道线性基是可以合并的,且具有交换律。因此容易想到线段树维护区间线性基,但是这样无法支持区间异或的操作。此时容易想到分块处理,但是这样时间复杂度变为 O(n\sqrt n\omega^2) 其中 \omega 为位数这里取 \omega=32,一看就不是能过得去的样子。

然后观察到一个特别厉害的性质:在线性基中集合 \lbrace a_l,a_{l+1},\ldots,a_r\rbrace 可以通过选取子集异或表示出的数的集合和 \lbrace a_l,a_l\oplus a_{l+1},\ldots,a_{r-1}\oplus a_r\rbrace 表示出的数的集合是等价的。因此维护 b_i=a_i\oplus a_{i+1} 然后在 b 上操作,这样一来修改操作只需要处理 lr+1 两个端点处的值即可。而查询的时候先求区间线性基,然后再把 a_l 的值添加到线性基里最后统一求值即可。而维护 a_l 这种端点值可以先差分然后丢到一个 BIT 里维护。

总时间复杂度为 O(n\log^2n\omega),可以通过该题。

2025.11.09

006. CF622F The Sum of the k-th Powers

通过作差可以发现答案是一个 k+1 次多项式的形式,因此想到 Lagrange 插值。将 x_i=i0\le i\le n)带入,有:

\begin{aligned} &\sum\limits_{i=0}^ny_i\prod\limits_{j\neq i}\frac{x-j}{i-j}\\ =&\sum\limits_{i=0}^ny_i(\prod\limits_{j=0}^{i-1}\frac{x-j}{i-j}\prod\limits_{j=i+1}^n\frac{x-j}{i-j})\\ =&\sum\limits_{i=0}^ny_i(\prod\limits_{j=0}^{i-1}(x-j)\prod\limits_{j=i+1}^n(x-j)\prod\limits_{j=0}^{i-1}\frac1{i-j}\prod\limits_{j=i+1}^n\frac1{i-j})\\ =&\sum\limits_{i=0}^ny_i(-1)^{n-i}\frac1{x!}\frac1{(n-x+2)!}\prod\limits_{j=0}^{i+1}(x-j)\prod\limits_{j=i+1}^n(x-j) \end{aligned}

后面这两个 \prod 一看就很能预处理,而前面的显然可以直接算。时间复杂度为 O(n\log n)。注意到 i^k 是积性函数,所以使用线性筛可以将其优化至严格 O(n) 求解。

007. P4593 [TJOI2018] 教科书般的亵渎

S(n,k)=\sum\limits_{i=1}^ni^k,则容易观察到该题要求的答案为:\sum\limits_{i=0}^mS(n-a_i,m+1)+\sum\limits_{i=0}^m\sum\limits_{j=i+1}^m(a_j-a_i)^{m+1}。后半部分可以暴力快速幂求解,而前半部分是 CF622F,直接套用上面的公式求解即可。

008. P14437 [Aboi 2077] I am a fluff

现在我只会 \forall i\in[0,n],q^i\not\equiv 1\pmod{998244353} 的情况,但是水过去了()什么时候会做了再补上

但是 q-binomial 好玩

009. P1993 小 K 的农场

严肃学习差分约束。

具体而言,对若干个不等关系可以考虑建图。假设不等关系形如 x_a-x_b\le c,那么从 ba 连一条边权为 c 的单向边。

同理,对于 x_a-x_b\ge c 的不等关系,考虑对不等式两边同时乘 -1 然后不等式变号,得到 x_b-x_a\le -c,从 ab 连一条边权为 -c 的单向边。

而对于 x_a=x_b 这样的关系,容易证明其和 x_a\ge x_b,x_a\le x_b 两个不等式同时满足是完全等价的,因此按照上面的两个方式分别建两条边即可。

从超级源点 S 向所有点 i=1\ldots n 都连一条边权为 0 的有向边,从 S 开始跑最短路即可。

而判断其无解当且仅当该图存在从 S 出发的负环,而若有解,其一组合法解即为最短路得到的 dis 数组。

判断负环可以使用 spfa 或者 bellman-ford 算法,时间复杂度均为 O(nm)

010. P7624 [AHOI2021初中组] 地铁

挺好玩的这题其实

首先容易观察到答案是一段连续的区间。因此对于答案区间 [l,r],考虑二分求解。这样的问题有两种策略:

这里采取第二种做法。

判断当前二分到的总长度 x 是否合法是简单的,直接差分约束做即可。但是问题是若当前不合法,则无法知道合法区间是在 x 的左侧还是在 x 的右侧,这里考虑使用一些巧妙的手法:

对于建边关系而言:

然后还有相邻两个元素之间的关系,对于 i=1\ldots n-1,其形如 x_i+1\le x_{i+1},容易发现该边边长和二分到的 x 没有任何关系。而对于 i=n 的特殊情况,此时有 x_n\le x_1+x-1x_n-x_1\le x-1,此时该边边长和 x 成一倍增加关系。

于是考虑在差分约束建边的时候,额外增加一个信息表示其和二分到的值 x 之间的关系,该关系的值一定是 -1,0,1 中的一个。然后若总长度为 x 是不合法的,则考虑找出其所在的负环(这个可以在 spfa 的过程中通过记录路径得到),容易证明若其负环上所有倍率边的和为 0,则此时不管怎么调整 x 的值都必然会存在负环,即必然无解。否则,若该值 >0,则通过把 x 的值往大调整就可以使得该负环上边权值的和增大直到其权值之和为正。

该值 <0 的情况同理,此时把 x 的值往小调整即可。

总时间复杂度为 O(nm\log V),可以通过该题。

2025.11.10

011. P14315 [Aboi Round 2] Faputa

考虑竞赛图 SCC 计数的经典结论:设 \deg_i 表示竞赛图上 i 点的入度,则该竞赛图 SCC 的数量即为:\sum\limits_{i=1}^n[\sum\limits_{j=1}^i\deg'_j=\binom i2],其中 \deg' 数组是 \deg 升序排序之后得到的数组。证明是显然的。

然后考虑动态的维护翻转一条边的指向 / 查询整张图 SCC 数量这两个操作,根据上面的结论,你只需要维护 \deg' 数组和其前缀和数组即可。此时先维护好 \deg 数组的信息(这是十分简单的),然后你只需要时刻保证 \deg' 有序,且修改的时候修改 O(1) 个位置即可。容易想到修改每个 \deg 数组内的值时,直接二分查找出 \deg' 数组第一个 / 最后一个值和 \deg_i 相同的位置然后修改。因为修改操作要么是 +1 要么是 -1 所以显然可以保证数组有序。

而现在还需要维护 \deg' 数组的前缀和,考虑线段树处理。容易发现每次操作其实就是找出两个位置 i,j,使得:

容易观察出此时前缀和值会被修改的区间一定是 [i,j-1][j,i-1],而要么是该段区间被整体 +1,要么是该段区间被整体 -1

现在还需要维护整体查询,这个查询到的值形如:

\sum\limits_{i=1}^n[\sum\limits_{j=1}^i\deg'_j=\binom i2]

考虑维护内层 \sum\limits_{j=1}^i\deg'_j=\binom i2 是否成立。因为当前已经可以快速维护 \sum\limits_{j=1}^i\deg_j' 的值了,所以只需要查询区间中值为 \binom i2 的数的个数。比较困难的点在于 \binom i2 的值对每个不同的 i 都不同。但是 \binom i2 对相同的 i 而言其值为定值,因此想到在数据结构上对每个 i 改维护 (\sum\limits_{j=1}^i\deg'_j)-\binom i2 的值,这样问题变为了区间 \pm 1 整体查 0 的数量,可以用树套树做到大常数 O(n\log^2n) 或者分块做到 O(n\sqrt n),均无法通过,只能拿到 75 分。

考虑进一步优化。你会发现 \sum\limits_{j=1}^i\deg_j'\ge \binom i2 这个不等式对于任意 i=1\ldots n 均恒成立,而上述式子的值取 0,当且仅当不等式取等号。因此整体查询 0 的数量这一条件可以修改为整体查询最小值的数量,这个是容易维护的。

在线段树上维护下列信息:

上述信息是容易 O(1) 维护的,所以直接拿线段树做这个东西,总时间复杂度为 O(n\log n),可以通过该题。

012. P14008 「florr IO Round 1」序列游戏

TBD

013. [CXOI2025] 我常常追忆过去

直接复制我的题解了()

Algorithm 0

输出样例,期望得分 0 分。

Algorithm 1

竞赛图有很多优美的性质,比如说:

通过这个性质可以得出下面的结论:对于一张给定的竞赛图而言,竞赛图的 SCC 数量为 \sum\limits_{i=1}^n{[\sum\limits_{j=1}^id_j'=\binom i2]},其中 d' 数组是图上所有点的入度 \deg 升序排序得到的新数组。

直接暴力枚举竞赛图上每条边的朝向,时间复杂度为 O(2^{n^2}),期望得分 0 分。

Algorithm 2

暴力枚举朝向还是太?了,有没有更正常的做法?

考虑枚举划分的位置 i(即对给定的 i 上面艾弗森括号内取等),计数该条件成立时的概率。考虑这个条件成立的另一个定义,其实就是要求该竞赛图上的某 i 个点组成的集合 L 连向另外 n-i 个点组成的集合 R 的边都是由 L 集合单向连向 R 集合的。很显然这个条件成立的概率就是 2^{-i(n-i)}。而从 n 个点中选 i 个点划分出 L,R 两个集合的方案数则是 \binom ni

因此 n 个点组成的竞赛图 SCC 数量的期望就是:

1+\sum\limits_{i=1}^{n-1}\binom ni2^{-i(n-i)}

可以 O(n^2) 对每个 n=1\ldots m 求答案。

Algorithm 3

能不能继续优化?

前置知识:NTT,Chirp-Z Transform(CZT)

套路的对上面的式子拆组合数,得到:

\begin{aligned} &1+\sum\limits_{i=1}^{n-1}\binom ni2^{-i(n-i)}\\ =&1+\sum\limits_{i=1}^{n-1}\frac{n!}{i!(n-i)!}2^{-i(n-i)}\\ =&1+n!\sum\limits_{i=1}^{n-1}\frac{2^{-i(n-i)}}{i!(n-i)!}\\ =&1+n!\sum\limits_{i=1}^{n-1}\frac{(\frac12)^{i(n-i)}}{i!(n-i)!}\\ =&1+n!\sum\limits_{i=1}^{n-1}\frac{(\frac12)^{\binom n2-\binom i2-\binom{n-i}2}}{i!(n-i)!}\\ =&1+n!(\frac12)^{\binom n2}\sum\limits_{i=1}^{n-1}\frac{(\frac12)^{\binom i2-\binom{n-i}2}}{i!(n-i)!}\\ =&1+n!(\frac12)^{\binom n2}\sum\limits_{i=1}^{n-1}\frac{(\frac12)^{-\binom i2}}{i!}\times\frac{(\frac12)^{-\binom{n-i}2}}{(n-i)!}\\ \end{aligned}

后半部分的和式是一个等和卷积的形式,可以用 NTT 做到 O(n\log n) 求解。总时间复杂度为 O(n\log n) 可以通过该题。

014. [CXOI2025] 曾经的日子无法重来

TBD

015. [CXOI2025] 我该在哪里停留

不想把这个笔记彻底卡死,所以直接挂链接了。

2025.11.11

018. [CXOI2025] 追忆宛如入梦

暂时不能公开题解

019. CF2163D1 Diadrash (Easy Version)

如果你不会做这个题,请注意题目询问的限制条件求的是 \max 而不是 \min()我绷不住了

$Q$ 次询问求答案是简单的。先考虑如何优化到 $n$ 次询问求解: :::success[使用 $n$ 次询问求答案] 根据上面的第一条性质,因为要求的是 $\text{mex}$ 值最大的区间,所以对于同一个 $l$ 而言,只需要保留这些区间中 $r$ 最大的区间即可。这样一来剩余 $n$ 个区间,直接全部询问一遍即可。 ::: 然后考虑利用第二个性质继续优化。 :::success[使用 $\lceil\frac n2\rceil+1$ 次操作求答案] 假设目前已经确定了序列中 $0$ 的位置是 $p$,那么这个时候若一个询问区间 $[l,r]$ 满足 $p\not\in[l,r]$,那么该区间的 $\text{mex}$ 值一定为 $0$,不再需要询问。在只保留 $n$ 个区间时: + 若 $p\le\lfloor\frac n2\rfloor$,则此时左端点在 $p$ 左侧的区间不超过 $\lfloor\frac n2\rfloor$ 个。 + 若 $p>\lfloor\frac n2\rfloor$,则此时右端点在 $p$ 右侧的区间不超过 $\lfloor\frac n2\rfloor$ 个。 问题在于如何确定 $p$。直接分治需要问 $O(\log n)$ 次,但是容易发现其实不需要知道 $p$ 的具体位置,只需要知道 $p$ 是在序列的左半边还是在序列的右半边即可,因此只需要询问区间 $[1,\lfloor\frac n2\rfloor]$ 判断该区间的 $\text{mex}$ 值是否为 $0$ 即可。总询问次数为 $\lceil\frac n2\rceil+1$,可以通过该题。 ::: ### 020. [CF2163D2 - Diadrash (Hard Version)](https://codeforces.com/contest/2163/problem/D2) :::success[MEX 的单调性] $\text{MEX}(a_1,a_2,\ldots,a_{n-1})\le \text{MEX}(a_1,a_2,\ldots,a_n)$。 ::: :::success[排列上 MEX 的性质] 对于一个给定的区间 $[l,r]$,有 $\text{MEX}(l,r)=\min(\text{MEX}(1,r),\text{MEX}(l,n))$。 ::: 请先阅读 D1 题解。 考虑利用上面的两个性质解题,使用二分解决这个问题。对于给定的左端点区间范围 $[l,r]$,找出中点位置 $mid=\frac{l+r}2$,两次询问出 $\text{MEX}(l,n)$ 和 $\text{MEX}(1,r)$ 的值,设两个值分别为 $suf$ 和 $pre$。 根据上面的性质,若有 $pre>suf$,则右侧的答案一定不可能比左侧的答案更大,往左二分处理,否则往右二分处理即可。 询问次数为 $2\log V\le 30$,满足题目限制,可以通过该题。 ### 021. [ABC421F - Almost Sorted 2](https://atcoder.jp/contests/abc431/tasks/abc431_f) 直接 dp 怎么看也有后效性做不了,所以考虑换个方向思考,用插入的顺序来维护答案。 考虑把所有元素按照从大到小的顺序排序并插入,对于某个要插入的元素的值 $x$,为了让序列合法,有两种可能的情况: + 插入在序列的最前面 + 其前一个元素的值的取值范围是 $[x,x+d]$(不用考虑比 $x$ 小的情况是因为按照值域倒序插入) 这个东西拿双指针维护一下就可以线性。然后还需要判掉相同元素的情况,这个东西除以一个阶乘即可。总时间复杂度还是线性的。 ### 022. [代码源 NOIp 模拟赛 Day4 二分图](http://oj.daimayuan.top/contest/423/problem/3424) 根据经典结论,一个连通块内最小不重复路径覆盖数的值为:记 $d$ 为该连通块内度数为奇数的点的数量,则覆盖数为 $\max(1,\frac d2)$(可以证明有 $2\mid d$)。 先考虑删除“只能同侧传输的影响”。此时若有 $k$ 个连通块,连通块内分别有 $d_1,d_2,\ldots,d_k$ 个点度数为奇数,则路径覆盖数显然为 $(\sum\limits_{i=1}^k\max(1,\frac {d_i}2))-1$。 然后考虑给每个连通块分类。记录一个连通块的型号分别为: + 欧拉分量:所有覆盖路径的起点和终点一定都在二分图的同侧 + `LL` 型:所有覆盖路径的起点和终点一定都在二分图的左侧 + `RR` 型:所有覆盖路径起点和终点一定都在二分图的右侧 + `LR` 型:存在一条覆盖路径,使得其起点和终点不在二分图的同侧 考虑如何判定一个连通块的型号。容易想到维护 $l_i$ 表示第 $i$ 个连通块中度数为奇数的左侧点的数量,$r_i$ 表示第 $i$ 个连通块中度数为奇数的右侧点的数量。 因为题目保证图是二分图这样的特殊图,因此每一条边都一定是从图的一侧经过一条边走到图的另一侧。因此在图上走一条路径,经过的左侧点的数量和右侧点的数量的差的绝对值一定不超过 $1$。 而又因为根据贪心的思想,显然在一个连通块内覆盖的路径数越少越好,因此容易判定连通块的型号: + 若 $l_i=r_i=0$,则该连通块为欧拉分量,其同时为 `LL` 型和 `RR` 型,对答案不会产生影响可以直接不管。 + 若 $r_i=0$,则连通块为 `LL` 型,此时路径全部为从左侧点出发到左侧点结束 + 若 $l_i=0$ 则同理该连通块为 `RR` 型,此时路径全部为从右侧点出发到右侧点结束。 + 否则连通块为 `LR` 型,此时必然存在一条路径从一侧点出发在另一侧点结束。 考虑什么时候取不到前面给出的答案下界 $(\sum\limits_{i=1}^k\max(1,\frac{d_i}2))-1$。容易观察到当且仅当同时存在 `LL` 型和 `RR` 型的连通块,且不存在 `LR` 型的连通块能快速的从二分图的一侧切换到二分图的另外一侧。此时需要一条额外的路径覆盖,答案会再增加 $1$。 容易想到用并查集维护连通块信息,总时间复杂度为 $O(\alpha(n+m))$,可以通过该题。 :::success[AC Code] ```cpp line-numbers namespace Loyalty { inline void init() { } int deg[N]; vector<int> scc[N]; inline void main([[maybe_unused]] int _ca, [[maybe_unused]] int atc) { int n, m; cin >> n >> m; DSU dsu; while (m--) { int a, b; cin >> a >> b; b += n; ++deg[a], ++deg[b]; dsu.merge(a, b); } for (int i = 1; i <= n + n; ++i) scc[i].clear(); for (int i = 1; i <= n + n; ++i) scc[dsu.find(i)].emplace_back(i); int scc_LR = 0, scc_LL = 0, scc_RR = 0, res = -1; for (int i = 1; i <= n + n; ++i) if (dsu.find(i) == i && dsu.siz[i] > 1) { int cnt_left = 0, cnt_right = 0; for (int &j : scc[i]) if (deg[j] & 1) ++((j <= n) ? cnt_left : cnt_right); int cnt_deg_odd = 0; for (int &j : scc[i]) if (deg[j] & 1) ++cnt_deg_odd; res += max(1ll, cnt_deg_odd >> 1); if (!cnt_left && !cnt_right) continue; if (!cnt_left) ++scc_RR; else if (!cnt_right) ++scc_LL; else ++scc_LR; } if (scc_LL && scc_RR && !scc_LR) ++res; cout << res << '\n'; } } ``` ::: ### 023. [代码源 NOIp 模拟赛 Day4 构造LIS](http://oj.daimayuan.top/contest/423/problem/3426) 复读了官方题解() 先考虑一些比较简单的构造。容易注意到构造下面的序列: `2 1 4 3 6 5 ... n n-1` 可以获得 $2^{\frac n2}$ 个 LIS。 于是考虑在上述构造的基础上扩展。还是先构造一个上面的序列,此时 LIS 的数量是不够的。这里考虑再额外插入一些元素: + 若 $x$ 在二进制下的第 $i$ 位为 $0$,则在上述构造序列中第 $2i$ 个数和第 $2i+1$ 个数之间插入 $2k+i+1

这样构造显然远远超过上限,所以考虑优化。容易想到换一个进制(这里使用 4 进制,构造序列的长度最小(即 \text{Base}\log_{\text{Base}}X 的值最小))。

直接这样构造需要使用 295 个字符,考虑继续优化。此时想到 NOI2024 超级富翁 的套路,把上面这个东西搞成混循环然后使用 dp 等手法找出最优解。可以计算出使用 4,5 混循环,且只用一个 5 可以取到答案下界 291,这样构造是简单的,能拿到 90 分。

考虑继续优化。注意到此时后缀上一定存在一些一定不会出现在 LIS 的位置可以直接删除,而删除之后必然存在 a_n=n 也同样可以删除,这样至少删除了 2 个位置,构造的序列长度为 289,可以通过该题。

总时间复杂度为 O(T\log X)

:::success[AC Code]

namespace Loyalty
{
    inline void init() { }
    inline i128 read()
    {
        i128 x = 0;
        char ch;
        while (!isdigit(ch = cin.get()))
            ;
        x = ch & 15;
        while (isdigit(ch = cin.get()))
            x = (x << 3) + (x << 1) + (ch & 15);
        return x;
    }
    int deg[N];
    vector<int> scc[N];
    inline void main([[maybe_unused]] int _ca, [[maybe_unused]] int atc)
    {
        i128 x = read();
        vector<int> a;
        int idx = 0;
        while (x > 4)
        {
            for (int i = 0; i < 4; ++i)
            {
                if (i == (x & 3))
                    a.emplace_back(-1);
                a.emplace_back(idx - i + 4);
            }
            x >>= 2, idx += 4;
        }
        for (int i = 0; i < x; ++i)
            a.emplace_back(idx - i + x);
        idx += x;
        for (int &i : a)
            if (i == -1)
                i = ++idx;
        cout << a.size() << '\n';
        for (int &i : a)
            cout << i << ' ';
        cout << '\n';
    }
}

:::

024. P14455 [ICPC 2025 Xi'an R] Imagined Holly

挺有意思的题。注意到两个点 u,v 相邻时,必然满足对于任意一个点 x,都有 \text{dist}(u,v)=\text{dist}(u,x)\oplus\text{dist}(v,x)

然后随便咋做就都能过了(比如说随机一个排列 p 然后按照 p 的顺序给每个点对暴力判一下,期望对不合法边判 O(1) 次,对合法边判 O(n) 次,总时间复杂度即为 O(n^2))。

025. P14449 [ICPC 2025 Xi'an R] Catch the Monster

这题真没啥思维含量吧,非常容易想到先考虑什么时候怪物会被抓住(后面是我的思考过程):

1)只有一个点,此时怪物必然在这个点上,直接选这个点怪物就必然被抓住。

2)树形如一条链,此时考虑从链的一头开始,按照顺序依次选链的每一个点。

此时假设当前已经按顺序选中了链的第 1\ldots i-1 点,此时怪物必然只能在 i\sim n 的点上。而若你此时选择了 i 点,则:

因此这个策略一定能杀死怪物。

3)树形如一个菊花图,即对于每个 2\le i\le n 都存在一条 1\leftrightarrow i 边。此时你有下面的策略:

因此这个策略一定能杀死怪物。

4)如果我在菊花图上挂一条链怎么样???

按照上图中给结点标号的顺序即可杀死怪物。策略即为:先在菊花上操作,把怪物逼到一条链上,然后按照链的策略击杀怪物。

5)如果我在菊花图上挂多条链,得到一个毛毛虫树怎么样?

按照上图给结点标号的顺序即可杀死怪物。策略即为:找出毛毛虫树上的中心链(在上图中即为 1-4-7-8-9-12-13),然后从上往下,若当前结点相邻的结点有叶子节点,那么就先用菊花图的策略把所有叶子结点干掉。然后用链的策略把当前结点也干掉继续往下递归,直到干完中心链上的所有点以及所有叶子结点为止。容易证明该策略必然正确。

6)如果在毛毛虫树的一个叶子结点上再加一个儿子,那又会怎么样?

注意到此时不存在一个能让怪物一定必死的策略,那只能下课死了(确信

然后容易观察到结论:若一个树的一个导出子图不能让怪物必死,那么这个树一定不能让怪物必死。然后你发现剩下满足条件的情况都是毛毛虫树,于是就容易得到 O(nq) 的求解方法(即判断在区间 [l,r] 内的边是否构成了一个毛毛虫树森林)。

然后考虑优化。注意到在固定左端点的时候移动右端点存在单调性(即若 [l,r] 不合法,则 [l,r+1] 一定不合法;[l,r] 合法,则 [l,r-1] 一定合法)。

因此考虑处理出 f 数组:f_i 表示若当前左端点为 i,则右端点最大为 f_i 可以保证边在 [i,f_i] 区间内,一定合法(即能让怪物必死)。于是容易想到双指针维护答案,每一次插入 r+1 位置的元素直到当前区间内的边不再合法,更新 f 数组的值然后删除 l 位置的元素。

问题在于如何快速的判断一个区间是否是合法的。考虑一个森林是毛毛虫森林的充要条件。容易观察到:

于是考虑从度数方面入手,动态维护若加入 / 删除当前区间内的边,森林是否仍然为 / 不为毛毛虫森林。直接加入一个点可能存在后效性(加入一个点 x,此时 x 必然为叶子结点,则其会影响和她相邻的唯一结点 y 的信息,而 y 又会影响到和其相邻的另一个点 w 的信息),维护起来比较麻烦。然后我编了一个奇奇怪怪的做法,又维护了另一个数组信息,表示每个点相邻的点在当前导出子图中的点编号的异或,根据异或的一些性质,可以快速的找出后效性产生的结点 w,而这个数组也同样是容易维护的。

于是这个题就做完了,时间复杂度根据实现优劣可以为 O(n+m+q)O(n+m\log n+q),均可通过该题。

2025.11.12

026. [CXOI2025]windy_yy's LCM-Counting

直接做是困难的,考虑反演。设 F(x) 表示在 x 的因子内选择 n 个数的方案数,G(x) 表示选择 n 个数,使得其 \text{LCM}x 的方案数。

F(x) 是容易的。设 d_x 表示 x 的所有因子组成的集合,则显然有 F(x)=\binom{|d_x|+n-1}{|d_x|-1}。而求 G(x) 是比较困难的,所以考虑 Mobius 反演,有 G(x)=\sum\limits_{i\mid x}\mu(\frac xi)F(i)。而此时答案即为 G(q)

考虑 \mu 函数的定义,容易知道 \mu(q) 不为 0 当且仅当其不存在一个平方因子。因此容易想到将 q 写成 q=p_1^{\alpha_1}p_2^{\alpha_2}\ldots p_k^{\alpha_k} 的形式。此时为了让 \frac qi 中不存在平方因子且满足 i\mid q,则 i 可以被写成 i=p_1^{a_1-\epsilon_1}p_2^{a_2-\epsilon_2}\ldots p_k^{a_k-\epsilon_k} 的形式,其中 \epsilon_1,\epsilon_2,\ldots,\epsilon_k\in\lbrace 0,1\rbrace

又因为 10^7 以内的整数最多有 2^{\omega(10^7)}=2^8=256 个不同的选择 \epsilon 数组的方案,因此预处理组合数,并线性筛出 \mu 函数后,总时间复杂度为 O(T\omega(10^7)2^{\omega(10^7)}),可以通过该题。

027. P5290 [十二省联考 2019] 春节十二响

先考虑一条链的情况。此时有两类形态:

第一类情况是简单的,显然任意两个点之间都有祖先关系,直接把所有点都单独划组,答案即为所有点的代价的和。

第二类情况就是合并两个链,链内所有元素还是单个分组,而两条链之间则可以两两分组。配对方式使用数学归纳 + Exchange-Argument trick 可以发现一定是让最大的组分别配对,剩下若干个元素单个配对最优。一遍排序即可做到 O(n\log n)

考虑扩展到树上。O(n^2\log n) 做法是简单的,直接在每个点上开一个堆记录当前点子树内所有组的代价,然后按照上面的贪心策略暴力合并即可。

此时注意到这个东西和合并的顺序无关,因此想到用启发式合并来优化,时间复杂度变为 O(n\log^2n),可以轻松通过。

028. P10832 [COTS 2023] 传 Mapa

题目给出了明确的提示“把 x_i,y_i 看成一个整体组来考虑”,于是容易想到 Lagrange 插值插出 f 函数,根据经典结论可知插出的函数恰有 n 项,然后再编一个大于 \max y_i 的模数(例如 10^9+7)在模意义下插值。此时 f 函数每一项的系数都不超过 2^{30},直接转成二进制扔到 s 串里传输即可。

而对于逆变换,直接套 Lagrange 插值板子即可。

029. P6790 [SNOI2020] 生成树

前置知识:广义串并联图

核心操作是:

然后注意到题目给定的图就是一个广义串并联图,因此考虑使用这个东西来计数。

在图上拓扑排序,队列内记录当前所有度数 \le 2 的点的编号。在每一条边上维护信息 (f,g) 表示当前边在 / 不在生成树上的方案数,每一次取出队首元素:

因为初始图的性质不光保证了图是广义串并联图,而且还保证了图删掉一条边是仙人掌,因此根据上面的拓扑排序处理法,最终必然会将图上所有的点和所有的边均删除。

总时间复杂度为 O(n)O(n\log n)(用 map 维护边)。

2025.11.13

030. P6898 [ICPC 2014 WF] Metal Processing Plant

正解很困难,但是注意到这是一个最优化问题,所以先考虑一个显然的贪心:按顺序插入每个物品然后分别计算其插入到两个集合内的代价,若插入到 A 集合里更优那么就插入到 A,否则就插入到 B。

这一看就很没有道理,但是这个时候直接随机一个顺序贪心然后多随机几次就能过了,应该是卡不了的。

031. P3641 [APIO2016] 最大差分

第一问是简单的,直接从两端往中间问即可。

对于第二问,先求出序列的最小值 mi 和最大值 mx,此时根据抽屉原理可知答案有下界 \frac{mx-mi}{n-1}

这个时候考虑给 [mi,mx] 这个区间每 \frac{mx-mi}{n-1} 个元素分块,根据抽屉原理可知最后 gap 最大的位置 a_{i+1}-a_i 一定不会让 a_ia_{i+1} 出现在同一个块内。因此直接对每个块询问一次最小值和最大值,然后若该段区间内有元素,就把答案和上一次询问出的最大值做个差并更新答案即可。最后记得处理 mx 和最后一次问出的最大值的差。

然后考虑证明正确性:

总花费为 (n+1)+(2n-3)=3n-2\le 3n,符合题目要求,可以通过该题。

032. P14495 [NCPC 2025] Arithmetic Adaptation

笑点解析:wa 了一发

记得判断你构造的整数 a,b 是否在 [-999,-1]\cup[1,999] 范围内

033. 代码源 NOIp 模拟赛 Day5 无向稀疏图上更快的最长路径

观察到路径的长度一定是不到 O(\log m) 级别的,所以考虑直接枚举起点然后暴力 dfs 出所有的路径,然后询问的时候直接查表即可。

直接搜肯定过不去,考虑剪枝:

时间复杂度不会证,但是能过而且跑的比正解快 :)

034. P5408 第一类斯特林数·行

考虑用第一类斯特林数的生成函数来解这个问题。设 f_x(n) 是第 n 行第一类斯特林数组成的生成函数,则有:f_x(n)=\sum\limits_{i=0}^n\begin{bmatrix}n\\i\end{bmatrix}x^i=\prod\limits_{i=0}^{n-1}(x+i)

现在要求 f_x(n) 的每一项系数,容易想到用分治 NTT 做到 O(n\log^2n),据说卡卡常能过。这里给出一种 O(n\log n) 求多项式系数的解法。

先考虑 n=2^p 的情况。此时容易想到倍增求解,即用 f_x(n) 的多项式系数直接推出 f_x(2n) 的多项式系数。

可以推式子:

\begin{aligned} f_x(2n) &=\prod\limits_{i=0}^{2n-1}(x+i)\\ &=\prod\limits_{i=0}^{n-1}(x+i)\times\prod\limits_{i=0}^{n-1}(x+n+i)\\ &=f_x(n)\times f_{x+n}(n) \end{aligned}

问题在于求 f_{x+k}(n) 的值。考虑用 f_x 来表示出 f_{x+k}(n) 的值:

\begin{aligned} f_{x+n}(n) &=\sum\limits_{i=0}^na_i(x+k)^i\\ &=\sum\limits_{i=0}^na_i\sum\limits_{j=0}^i\binom ijx^jk^{i-j}\\ &=\sum\limits_{j=0}^n\sum\limits_{i=j}^na_i\binom ijx^jk^{i-j}\\ &=\sum\limits_{j=0}^nx^j\sum\limits_{i=j}^na_ik^{i-j}\frac{i!}{j!(i-j)!}\\ &=\sum\limits_{j=0}^n\frac{x^j}{j!}\sum\limits_{i=j}^na_ik^{i-j}\frac{i!}{(i-j)!}\\ &=\sum\limits_{j=0}^n\frac{x^j}{j!}\sum\limits_{i=j}^n(i!a_i)\times(\frac{k^{i-j}}{(i-j)!}) \end{aligned}

A_i=i!a_i,B_i=\frac{k^{i-j}}{(i-j)!},则后半段和式是等差卷积的形式,可以翻转 B 数组然后令 C=A\odot B(即 CA,B 两个数组的卷积),前半部分的 \frac1{j!} 以及 A 内的 i!B 内的 i!,k^i 等部分都可以线性递推。

然后再考虑 n\neq 2^p 的情况。此时考虑类比快速幂的求法:

考虑使用主定理来分析时间复杂度,有:T(n)=T(\frac n2)+O(n\log n)=O(n\log n),显然可以轻松通过该题。

一个要注意的点是模数 2^{25}\times5+1 的原根为 3,不是 5

035. P5395 第二类斯特林数·行

这个比上个题简单的不是一点半点吧()

根据第二类斯特林数的经典通项公式可知:

\begin{Bmatrix}n\\m\end{Bmatrix}=\sum\limits_{i=0}^m\frac{i^n(-1)^{m-i}}{i!(m-i)!}

A_i=\frac{i^n}{i!},B_i=\frac{(-1)^i}{i!},则考虑令 C=A\odot BCA,B 两个数组的卷积,此时上面式子的答案就是 C_m

而题目要求的是一行的第二类斯特林数,容易发现求的就是 C_0,C_1,\ldots,C_n 的值,可以一遍 NTT 卷积求得。总时间复杂度为 O(n\log n) 可以通过该题。

036. P10756 [COI 2023] Sličnost

直接暴力是 O(Qn^3) 的,可以用一些手段优化到 O(Qn^2),但是显然没有前途。

考虑一个经典技巧:

此时可以发现,问题被转化为:

有一个 (n-k+1)\times(n-k+1) 的二维格点网格和 m 个矩形,你要求出一个格点,使得这个格点被矩形覆盖次数最多。

然后还有 Q 次询问,每一次询问会对 O(1) 个矩形的横坐标方向做最多 1 单位的修改(即不动 / 左右平移 1 个单位),执行完操作之后你还是要求一个格点使得这个格点被矩形覆盖次数最多。

可以想到对每一行开一个线段树来维护每个点的信息,但是这显然是开不下的。注意到相邻两行的线段树之间修改次数的和是 O(n) 级别的,因此想到开一个有 n 个版本的可持久化线段树维护维护每个横坐标的信息(区间最大值和区间最大值的出现次数),再维护一个线段树来维护可持久化线段树上每个版本的答案。

查询操作就是对可持久化线段树上每个版本的答案全部合并起来求最大值和最大值出现次数,这个东西可以很好的在线段树上维护。

然后还有修改操作。因为只会修改 O(1) 个矩形,且此时只会修改横坐标,而横坐标又只会进行最多 1 单位的修改,因此直接暴力修改边界上最多 2 个横坐标的信息(直接在可持久化线段树上修改对应版本,然后同时更新线段树上的最值信息),查询同样还是查线段树根结点即可。

一些比较难受的地方是可持久化线段树空间要开足(虽然这个题空间限制是 1G 但是还是很卡空间,不能全开 long long)。

2025.11.14

037. CF2085F2 Serval and Colorful Array (Hard Version)

先考虑比较暴力的做法。枚举选择的子序列最中间的位置 i,这样一来根据经典贪心结论,左右两侧都分别需要选 \frac k2 个(对 2\mid k 的情况需要处理左边多选一个还是右边多选一个)。对每个值不和 i 相等的值,求出 i 左右两侧第一个和其值相同的位置 l_j,r_j,贪心排序选取就可以做到 O(n^2\log n)

考虑优化该算法。容易发现可以去掉在左右两侧分别选 \frac k2 个数的限制,直接贪心取 \min 放即可。证明的话考虑一个位置左右两侧的元素个数如果不相同那么一定可以调整到更优,因此最小值一定不会被漏过去。这样就可以优化到 O(n^2) / O(nk) 解决。

考虑从左往右扫描位置 i。注意到对每个值 j,在 i 右移一个单位的时候 j 的贡献的变化必然为 -1,0,1 中的一个。然后还可以发现若当前元素的值在中心位置之前则贡献的变化为 -1,在中心位置之后则贡献为 1。而特殊的,若 2\mid k,则需要特殊处理:此时存在两个中心,在两个中心变化的过程中该位置的贡献不会变化。

注意到贡献变化相同的位置是一段区间,写一个二阶差分即可 O(n) 维护答案的变化,可以通过该题。

038. P5396 第二类斯特林数·列

题目要求的东西太别扭了(你猜为什么我 wa 了一发),这里改一下变量名的顺序:

给定 n,k,对 i=0\ldots k,求 \begin{Bmatrix}i\\n\end{Bmatrix} 的值。

第二类斯特林数的生成函数形式是:

F(n)=\sum\limits_i\begin{Bmatrix}i\\n\end{Bmatrix}x^i

考虑推一下式子:

\begin{aligned} F(n) &=\sum\limits_i\begin{Bmatrix}i\\n\end{Bmatrix}x^i\\ &=\sum\limits_i(n\begin{Bmatrix}i-1\\n\end{Bmatrix}+\begin{Bmatrix}i-1\\n-1\end{Bmatrix})x^i\\ &=n\sum\limits_i\begin{Bmatrix}i-1\\n\end{Bmatrix}x^i+\sum\limits_i\begin{Bmatrix}i-1\\n-1\end{Bmatrix}x^i\\ &=n\sum\limits_i\begin{Bmatrix}i\\n\end{Bmatrix}x^{i+1}+\sum\limits_i\begin{Bmatrix}i\\n-1\end{Bmatrix}x^{i+1}\\ &=x(nF(n)+F(n-1))\\ \end{aligned}

F(n)=xnF(n)+xF(n-1),移项可得 (1-xn)F(n)=xF(n-1)F(n)=\frac{xF(n-1)}{1-xn}

这是一个递推式的形式,考虑直接求出其通项。显然有 F(n)=\dfrac{x^n}{\prod\limits_{i=1}^n(1-ix)}=x^n\times(\prod\limits_{i=1}^n(1-ix))^{-1}

前面的 x^n 就是多项式整体平移 n 位,可以先不考虑,因此只剩下后半部分的 (\prod\limits_{i=1}^n(1-ix))^{-1} 要求。而这个东西的内层显然可以分治卷积做到 O(n\log^2n) 求解,外层就套一个多项式求逆即可。总时间复杂度为 O(n\log^2n),可以通过。

039. P6012 [P5087] 数学 加强版

这是不是生成函数板子啊/yun

容易发现答案就是 [x^k]\prod\limits_{i=1}^n(a_ix+1),这个东西形如 n 个多项式相乘,使用分治 NTT 即可。因为模数非 NTT 友好所以可以使用拆系数的 FFT 求解。

总时间复杂度为 O(n\log^2n),可以通过该题。

2025.11.15

040.

041. P8321 『JROI-4』沈阳大街 2

先把期望成求和问题。

考虑一个非常厉害的转化:把两个序列 a,b 拼接到一起得到序列 c,这样问题就变为了在序列 c 上两两配对求所有最小价值的和。容易发现此时 c 序列上顺序是无所谓的,因此对 c 序列所有元素的值从大到小排序,此时一组配对对答案的贡献就是在序列上靠后的元素对答案的贡献。

考虑 dp。设 f_{i,j} 表示当前匹配了 c 序列的前 i 个元素,当前已经匹配了 j 对,所有合法匹配方案的价值的和。此时显然有初始条件 f_{i,0}=1,答案为 f_{2n,n}

考虑转移。对于枚举的 i,j,现在要求出 f_{i,j} 的值。那么有两种转移方法:

最后将答案乘以 (n!)^{-1} 即可。总时间复杂度为 O(n^2),可以通过该题。

042. 代码源 NOIp 模拟赛 Day6 诅咒

弱化版是 QOJ8723 2024北京市赛 乘二。

考虑优化。注意到该题中堆的操作都比较特殊,因此使用 [P2827 [NOIP 2016 提高组] 蚯蚓](https://www.luogu.com.cn/problem/P2827) 一题的套路,用两个有序队列 $q_1,q_2$ 来模拟出一个堆。具体细节见代码。 一些特殊情况是要特判 $k=1$ 和 $\min a=0$ 的 corner case。 :::success[AC 代码] ```cpp line-numbers namespace Loyalty { int a[N]; queue<int> q1, q2; inline void push(int x) { q2.emplace(x); } inline int top() { int res = inf; if (q1.size()) res = q1.front(); if (q2.size()) res = min(res, q2.front()); return res; } inline void pop() { int o = top(); if (q1.size() && q1.front() == o) q1.pop(); else q2.pop(); } inline void init() {} inline void main([[maybe_unused]] int _ca, [[maybe_unused]] int atc) { int n, m, k, s = 0; cin >> n >> m >> k; for (int i = 1; i <= n; ++i) cin >> a[i]; if (!m) { int s = 0; for (int i = 1; i <= n; ++i) s = (s + a[i]) % mod; cout << s << '\n'; return; } if (k == 1 || !a[1]) { int res = 0; sort(a + 1, a + n + 1); for (int i = 1; i <= n; ++i) res = (res + a[i]) % mod; cout << res << '\n'; return; } sort(a + 1, a + n + 1); for (int i = 1; i <= n; ++i) q1.emplace(a[i]); while (1) { int t = top(); if (t * k <= a[n]) { pop(); push(t * k); if (!--m) break; } else break; } // for (int i = 1; i <= n; ++i) // a[i] = top(), pop(); // sort(a + 1, a + n + 1); // for (int i = 1; i <= n; ++i) // q1.emplace(a[i]); for (int i = 1; i <= m % n; ++i) { int t = top(); pop(); push(t * k); } for (int i = 1; i <= n; ++i) a[i] = top(), pop(); for (int i = 1; i <= n; ++i) a[i] = a[i] % mod * power(k, m / n, mod) % mod, s = (s + a[i]) % mod; cout << s << '\n'; } } ``` ::: ### 044. [代码源 NOIp 模拟赛 Day6 区间](http://oj.daimayuan.top/contest/426/problem/3444) 原题是 [P12448 [COTS 2025] 观草 / Trava](https://www.luogu.com.cn/problem/P12448)。 问题询问的式子是 $\sum\limits_{i=1}^{n-k+1}\max\limits_{i\le j\le i+k-1}a_j$,看上去十分不能维护。于是考虑对这个式子做一点小小的转化: $$ \begin{aligned} &\sum\limits_{i=1}^{n-k+1}\max\limits_{i\le j\le i+k-1}a_j\\ =&\sum\limits_{1\le p\le V}\sum\limits_{i=1}^{n-k+1}[\max\limits_{i\le j\le i+k-1}a_j\ge p]\\ =&(n-k+1)V-\sum\limits_{1\le p\le V}\sum\limits_{i=1}^{n-k+1}[\min\limits_{i\le j\le i+k-1}a_j<p]\\ \end{aligned} $$ 上面的分析套路的把一个序列转化成了 $01$ 序列。现在考虑在这里搞事情。设另一个序列 $b$ 满足 $b_i=[a_i<p]$(对每个 $p$ 而言 $b$ 序列都是相同的),此时上面艾弗森括号内的条件就变为了 $[\min\limits_{i\le j\le i+k-1}b_j]$ 即 $\forall j\in[i,i+k-1],b_j=\text{True}$。因此想到只考虑 $b$ 序列中值为 $1$ 的位置。设开头结尾和每一个 $b$ 序列中 $1$ 的位置将序列分隔为了 $x$ 段,长度分别为 $len_1,len_2,\ldots,len_x$,则答案即为:$(n-k+1)V-\sum\limits_{1\le p\le V}\sum\limits_{i=1}^x\max(0,len_i-k+1)$。 此时这个东西仍然是没法求的。考虑到 $len$ 数组的定义就是序列中的极长全 $0$ 段。考虑按照从小到大的顺序扫描 $p$,经典结论(也可以均摊分析)可以发现每个位置的值最多会变化一次,也就是说不同极长全 $0$ 段是 $O(n)$ 级别的,于是想到拿单调栈对每个 $i$ 维护出其为最值时的极长区间 $[l_i,r_i]$,然后做一个后缀修改即可。 然后你还需要维护一个形如 $\max(0,len_i-k+1)$ 的式子,这是 [P3586 [POI 2015 R2] 物流 Logistics](https://www.luogu.com.cn/problem/P3586) 的经典套路,可以维护两个树状数组然后做个差求和做到小常数单 $\log$ 求解。 单点 $+1$ 也是简单的,考虑到这样只会更改 $O(1)$ 个极长全 $0$ 段,因此想到直接左右两个线段树二分求出该元素对应的极长 $0$ 区间然后将其分割为两个极长全 $0$ 区间,分别在树状数组和线段树上做修改即可。总时间复杂度为 $O(n\log n)$,可以通过该题。 ## 2025.11.16 ### 045. [P11536 [NOISG 2023 Finals] Curtains](https://www.luogu.com.cn/problem/P11536) 根据 CF2163D1 的结论可知,对于一个左端点,只有右端点最大的区间是有用的,因此只需要保留最多 $n$ 个区间。 考虑对询问区间和给定的区间分别排序然后双指针扫描,对于一个询问区间,将右端点在其右端点左侧的覆盖区间加入到线段树中,则此时询问区间 $[ql,qr]$ 合法的充要条件是: + $[ql,qr]$ 中每个位置都被至少覆盖一层。 + $[ql,qr]$ 中所有位置对应的覆盖左端点的最大值的 $\min$ 值 $=ql$。 发现这个东西就是区间取 $\max$ 区间查询 $\min$,可以简单线段树维护,总时间复杂度为 $O(n\log n)$ 可以通过该题。 ### 046. [P10045 [CCPC 2023 北京市赛] 线段树](https://www.luogu.com.cn/problem/P10045) 没见过就不会做系列 因为 $a_i$ 时刻为奇数,所以考虑把 $a_i$ 表示成 $2b_i+1$ 的形式。此时区间 $[l,r]$ 的乘积即可以表示为 $\prod\limits_{i=l}^r(2b_i+1)$ 的形式。又因为答案是对 $2^{20}$ 取模的,且有 $2\mid 2b_i$,因此上面的这个式子只用考虑有最多 $19$ 个选 $2b_i$ 的情况。 考虑直接用线段树维护这个东西。记 $cnt_i$ 表示在当前线段树结点对应区间 $[l,r]$ 上选 $i$ 个 $2b_i$ 作为乘积的方案数。合并枚举左右两侧选择的 $2b_i$ 数量然后简单计算即可。根据上面的分析只需要维护 $i=0\ldots 19$ 时的 $cnt_i$ 值即可。 总时间复杂度为 $O(nk^2\log n)$,其中 $k$ 取 $20$,卡常后可以过。 一个卡常技巧是答案用 `unsigned int` 存,众所周知 `unsigned int` 的乘法取模比 `int` 快,而且这个东西对 $2^{32}$ 自然溢出,然后就可以卡着时间限制通过。谴责出题人卡常。 ### 047. [[CXOI R5-A] Decomposition](https://www.luogu.com.cn/problem/T676772?contestId=290075) 计算初始答案是简单的,发现题目问的东西就是对每个结点 $i$,求 $1\sim i$ 路径上的左链数量。考虑一次树上 dp 求出 $siz_i$ 表示 $i$ 点为根的子树的结点数量,$f_i$ 表示 $1\sim i$ 路径上的左链数量,这两个信息都是可以 $O(n)$ 求出的。因此初始答案即为 $\sum\limits_{i=1}^nf_i$。 然后还有 $k$ 次查询操作,考虑经典 exchange-argument 技巧,对一个同时拥有左右儿子的结点 $u$,考虑计算若交换两个儿子,则对答案会额外产生多少贡献($\Delta$)。设其左右两个儿子的子树大小分别为 $s_l,s_r$,则有 $\Delta=s_l-s_r$。 把所有 $\Delta$ 排序然后贪心选前 $k$ 大且 $\Delta$ 为正数的结点交换左右儿子即可,总时间复杂度为 $O(n\log n)$ 可以通过该题。 ### 048. [[CXOI R5-B] Calculator](https://www.luogu.com.cn/problem/T683746?contestId=290075) 你一定会用牛顿迭代法开根。 ### 049. [P14518 [NFLSPC #8] APLSPC](https://www.luogu.com.cn/problem/P14518) 怎么还有 Quine,并非 OI 题,就不写题解了,可以去看第一篇题解。 ## 2025.11.17 ### 050. [P7962 [NOIP2021] 方差](https://www.luogu.com.cn/problem/P7962) 考虑沿用 CBC022E 的套路,把题面中给的操作扔到 $a$ 的差分数组里,容易发现其实就是交换两个相邻的数的差分值。 套路的把方差乘以 $n^2$ 拆成 $n\sum\limits_{i=1}^na_i^2-(\sum\limits_{i=1}^na_i)^2$ 的形式,然后考虑 dp 处理。设 $f_{i,j}$ 表示当前处理完了前 $i-1$ 个差分值,此时平方项的和值为 $j$,最少花费是多少。把方差的形式用差分的前缀和的形式展开然后合并同类项,可以发现最后得到的式子是一个单峰的形式,因此对于前面给出的 dp,插入一个新的元素要么插入到其最左侧要么插入到其最右侧。转移是容易的。 直接这样做时间复杂度为 $O(n^2a)$,难以通过。考虑继续优化。注意到 dp 上值不为 $0$ 的数是很少的,所以考虑只转移 dp 不为 $0$ 的情况,容易计算得出这样的位置的数量是只有 $O(a)$ 个的。因此此时总时间复杂度为 $O(na^2)$,可以通过该题。 ### 051. [P3430 [POI 2005] DWU-Double-row](https://www.luogu.com.cn/problem/P3430) 感觉是挺套路的题,考虑相邻位置之间建边,若值为 $i$ 的两个位置分别出现在第 $x$ 列和第 $y$ 列,那么: + 若两个位置出现在同一行,则必须换,在 $x,y$ 之间连一条权值为 $1$ 的双向边。 + 若两个位置不出现在同一行,那么必须相对不换,在 $x,y$ 之间连一条权值为 $0$ 的双向边。 然后容易发现这是个二分图。直接二分图染色,对每个连通块把出现次数少的那个颜色对应的所有位置全部换位置即可满足条件。 总时间复杂度为 $O(n)$,可以通过该题。 ### 052. [QOJ 5 在线 $O(1)$ 逆元](https://qoj.ac/problem/5) 科技的力量.jpg 前置知识:[Farey 序列](https://oi-wiki.org/math/number-theory/stern-brocot/)以及其相关理论,根号平衡。 这里稍微提一下什么是 Farey 序列 以及其性质: + $F_n$ 是第 $n$ 阶的 Farey 序列,序列中存储所有不可约的分母 $\le n$ 的值在 $[0,1]$ 之间的分数(这里认为 $\frac01,\frac11$ 也是不可约的分数)按照其值从小到大排序得到的有序分数序列。 + 性质 $1$:对于 $F_{m-1}$ 序列上任意两个相邻分数 $\frac ab,\frac cd$,在 $F_m$ 序列上一定**按照顺序连续的**存在分数 $\frac ab,\frac{a+c}{b+d},\frac bd$。 + 推论 $1$:对于 $F_m$ 中任意两个相邻分数 $\frac ab,\frac cd$,必然有 $bc-ad=1$。 + 性质 $2$:$|F_n|=1+\sum\limits_{i=1}^n\varphi(i)$。 + 性质 $3$:对任意整数 $n\ge 2$ 和任意实数 $v\in[0,1]$,一定可以在 $F_{n-1}$ 序列中找到至少一个分数 $\frac xy$,满足 $|v-\frac xy|\le \frac1{yn}$。 + 推论 $3$:分数 $\frac xy$ 一定是数 $v$ 在序列 $F_{n-1}$ 中向左查或者向右查能找到的第一个分数。 + 性质 $4$:对于 $F_n$ 序列内的每个分数 $\frac xy$,$\lfloor\frac{xn^2}{2y}\rfloor$ 的值互不相同。 回到这个题目。考虑固定 $n$,记 $v=\frac ap$。根据上面得到的性质和推论,可以知道此时必然存在一个分数 $\frac xy\in F_{n-1}$ 满足 $|\frac ap-\frac xy|\le\frac1{yn}$。此时把不等式的左右两侧同时乘以 $py$(显然有 $py>0$),得到:$|ay-px|\le\lfloor\frac pn\rfloor$。 记 $u=ay-px$,则此时又能得出两个结论: + $ay\equiv u\pmod p

考虑把上面提到的结论 1 改写,得到:y\equiv u\times a^{-1}\pmod p。因为这里想要求的是 a 在模 p 意义下的逆元即 a^{-1},因此想到在同余式两侧同时乘以 u^{-1},得到:a^{-1}\equiv u^{-1}\times y\pmod p

然后再利用结论 2|u|\le\lfloor\frac pn\rfloor 也就是 u 的绝对值很小,因此对这样的 a 只需在对应 Farey 序列上找出其对应的分数 \frac xy,计算 u=ay-px,即可套用公式 a^{-1}\equiv u^{-1}y\pmod p 解决。而此时 u^{-1} 可以直接线性求逆元。

然而此时存在 u<0 的情况。对这个东西的处理是比较容易的,有 p^{-1}\equiv -(-p)^{-1}\pmod p 然后转化成上一种情况了。

问题在于怎么快速构造 n 阶 Farey 序列 F_n。利用性质 2 可知 |F_n|=1+\sum\limits_{i=1}^n\varphi(i) 这个东西是 O(n^2) 量级的,而利用性质 4 可以想到开桶做到 O(V)=O(n^2) 排序,因此构造 F_n 的总时间复杂度为 O(n^2)

而现在还需要找出 n 阶 Farey 序列上一个分数 \frac xy 的前驱 / 后继分数,容易想到直接在值域上维护前缀和,然后简单处理即可 O(n^2)-O(1) 查询。

因此总时间复杂度分为两个部分:

因此总时间复杂度为 O(n^2+\lfloor\frac pn\rfloor),根号平衡一下就是 n^2=\lfloor\frac pn\rfloor 解得 p=n^{\frac13},此时时间复杂度为 O(p^{\frac23}+Q)

因为是板子所以挂个 AC 代码。

:::success[AC 代码]

#include "inv.h"
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2100010;
int pre[N], idx, mod, n, m, Inv[N];
pair<int, int> frac[N], farey[N];
#undef int
void init(int p)
#define int long long
{
    n = cbrt(p), m = n * n;
    for (int i = 1; i < n; ++i)
        for (int j = 0; j <= i; ++j)
        {
            int val = j * m / i;
            if (!pre[val])
                pre[val] = 1, frac[val] = {j, i};
        }
    for (int i = 1; i <= m; ++i)
        pre[i] += pre[i - 1];
    idx = 0;
    for (int i = 0; i <= m; ++i)
        if (frac[i].first || frac[i].second)
            farey[idx++] = frac[i];
    Inv[0] = Inv[1] = 1;
    for (int i = 2; i <= p / n; ++i)
        Inv[i] = Inv[p % i] * (p - p / i) % p;
    mod = p;
}
#undef int
int inv(int a)
#define int long long
{
    int val = a * m / mod;
    if (pre[val] < idx)
    {
        auto [x, y] = farey[pre[val]];
        int w = a * y - x * mod;
        if (abs(w) <= mod / n)
        {
            int inv_t;
            if (w >= 0)
                inv_t = Inv[w];
            else
                inv_t = (mod - Inv[-w]) % mod;
            return y * inv_t % mod;
        }
    }
    if (pre[val] - 1 >= 0)
    {
        auto [x, y] = farey[pre[val] - 1];
        int w = a * y - x * mod;
        if (abs(w) <= mod / n)
        {
            int inv_t;
            if (w >= 0)
                inv_t = Inv[w];
            else
                inv_t = (mod - Inv[-w]) % mod;
            return y * inv_t % mod;
        }
    }
    throw;
    return -1;
}

:::

2025.11.18

053. QOJ11532 Rikka with Employees

比较厉害的题。

先考虑菊花的情况怎么做。初始根结点肯定就是快乐的。而此时容易想到把菊花的所有叶子编号得到一个序列,然后在序列上分治。

对于当前的分治区间 [l,r],此时分治区间之外的结点全部在栈内。此时求出中间位置 mid=\frac{l+r}2,需要分别使得区间 [l,mid] 和区间 [mid+1,r] 内所有结点是快乐的。容易想到对区间 [l,mid],只需要让 [mid+1,r] 区间内的人全部放假,就可以向区间 [l,mid] 递归处理,反之同理。这样操作一旦递归到的区间 [l,r] 满足 l=r,那么此时 l 就一定是快乐的。容易证明该算法满足操作次数限制。

然后再考虑链的情况怎么做。这个是简单的,直接从一端出发往另一端扫一遍然后逆序弹栈即可。

考虑将其推广到一般情况。此时可以想到类比上面的结构,把选择中间位置 mid 改成选择当前根节点,但是这显然可以被卡爆。此时考虑一个十分厉害的做法:考虑重链分治,这样可以看做是重链上挂了若干个子树,因此对重链跑链的做法,剩下的部分跑子树的做法即可。(啥????)

因为每个结点都最多跳 O(\log n) 次重链就可以达到根节点,因此这样分治树高是 O(\log n) 的卡不掉,满足操作次数限制,然后就过了。

054. P6130 随机红包

积分我哪会啊,已严肃背过结论

答案是 \large\frac1n\sum\limits_{i=n-k+1}^i\frac1i,线性递推出逆元然后用前缀和就可以做到 O(n)-O(1)

055. 讨论区里看到的题

给定 n,m,有 f(n)=\sum\limits_{i=1}^n\binom miA_n^i,求递推公式。

直接暴力做是 O(n^2) 的,容易想到把组合数和排列数全拆开,得到:

\begin{aligned} f(n) &=\sum\limits_{i=1}^n\binom miA_i^n\\ &=\sum\limits_{i=1}^n\frac{m!}{i!(m-i)!}\times\frac{n!}{(n-i)!}\\ &=n!m!\sum\limits_{i=1}^n\frac1{i!(m-i)!(n-i)!} \end{aligned}

A_i=\frac1{i!(m-i)!},B_i=\frac1{i!},则 C=A\odot B 即为 A,B 两个多项式的等和卷积,式子后面的和式的值就是 C_n-1(和式的下界是 1 不是 0),用 NTT 可以做到 O(n\log n)

考虑进一步优化。上面有定义

A_i=\frac 1{i!(m-i)!},B_i=\frac1{i!}

把这个东西写成生成函数的形式,有:

对这个东西做卷积,得到 C(x)=A(x)B(x)=\frac1{m!}(1+x)^me^x

g(x)=\sum\limits_{n\ge 0}c_nx^n=(1+x)^me^x,有 c_n=m!C_n,C_n=\frac1{m!}c_n,这个时候有 f_n=n!c_n-1c 这个系数数组可以多项式 ln exp 单 log 解出来

继续优化可以对 g 求导,得到 g'(x)=m(1+x)^{m-1}e^x+(1+x)^me^x=(1+\frac m{1+x})g(x)(m+x+1)g(x)=(1+x)g'(x),两个多项式相等当且仅当其系数均相等,把 g' 展开可以得到

g'(x)=\sum\limits_{n\ge 0}nc_nx^{n-1}\sum\limits_{n\ge 0}(n+1)c_{n+1}x^n

然后有

(m+x+1)g(x)=(m+1)g(x)+xg(x)=(m+1)\sum\limits_{n\ge 0}c_nx^n+\sum\limits_{n\ge 1}c_{n-1}x^n

以及

(1+x)g'(x)=g'(x)+xg'(x)=\sum\limits_{n\ge0}((n+1)c_{n+1}x^n)+\sum\limits_{n\ge1}c_{n-1}x^n

现在要解方程让两个多项式的系数相等,就是:

然后因为要求的是 f_n 所以把 f_n 代进去(后面那个 -1 太麻烦了先不管了),得到:

f_{n+1}=(m-n+1)f_n+nf_{n-1}

然后处理一下边界就是 f_0=1,f_1=m+1,可以线性递推。

056. 代码源 NOIp 模拟赛 Day 7 某道题的checker

高维前缀和是什么来着()

容易发现,(a_i\text{ OR }x)<(a_{i+1}\text{ OR }x) 可以拆解为:

这个条件不够充分,考虑再加一点限制条件:

然后发现这个限制其实形如:

发现这个形式看着就很高维前缀和,但是高维前缀和无法处理“某一位的值必须为 0”这里条件。

容易想到把上面的那个条件转化为“当前位随便填 - 当前位不能填 1”,这样可以容斥一下转化为只有必须填 1 的条件,然后可以简单高维前缀和维护。

057. 代码源 NOIp 模拟赛 Day 7 dfsize

那一天的卡常毁掉了我写题解的心情

058. Lemansky 大神的题

题意:给定一个 n\times m 的矩阵 a,有 Q 次操作,每次操作要么是给定一个子矩阵,翻转子矩阵内的所有行,要么是给定一个子矩阵,翻转子矩阵中的所有列,要么是询问一个位置对应的值,要么是询问一个值对应的一个位置。1\le n,m,Q\le 5000

哈哈哈哈哈被诈骗了

你可能会想到去维护行列变化的信息然后发现根本维护不了,然后你会想到上二维 ds 或者 KDT 然后你会发现还是做不了

然后你注意到 Q 很小而且查询都是单点,于是你想到直接对每个点暴力模拟一遍之前执行的所有操作,然后你发现这个是 O(Q^2) 的直接过了。。。

059. [CXOI R5-C] Permutation

质量很高的题

容易观察到题目中给出的权值其实指的就是序列的 LDS 长度,而因为题目中又给出了逆序关系,因此考虑这样的一个构造:

容易证明这个构造可以覆盖所有有解的输入。

然后考虑 dp 处理,设 f_{i,j,p} 表示当前处理了前 i 段,共用了 j 个位置,逆序对数量为 p 是否可行,构造方案直接在 dp 的过程中记录转移路径即可。

时间复杂度为 O(Tn^5),但是这个东西非常好剪枝,而且跑不满常数也很小,所以 1e9 也能 1s 轻松冲过去。

(Fun fact:有个人坚定的相信这个题是折半搜索)

060. [CXOI2025] MinMex Problem

已严肃被诈骗

注意到若一个序列的 \min 不为 0,则其 \text{mex} 一定为 0,因此 \min 乘以 \text{mex} 的值不论序列长什么样值都是 0

然后你就可以支持 O(1) 的区间加区间推平区间翻转区间询问了。

2025.11.19

061. P9920 「RiOI-03」变换,反演

Subtask 1

注意到 g(n)=d(n),此时有 f(n)=\sum\limits_{d\mid n}\mu(d)d(\frac nd)=1

考虑证明该结论。上面这个东西是 \mu 函数和 d 函数的狄利克雷卷积,众所周知 \mud 都是积性函数,所以 f(n)=\mu * d 也同样是积性函数。根据经典套路可知,积性函数只需要求出 f(p)p 是质数)的值就可以直接推出其他所有值(例题:CBC013E,给定 k 求所有 i^k 的值,f(x)=x^k 是完全积性函数,可以通过筛出质数位置的值然后递推出来)

f(p) 的值是容易计算的。显然有 f(p)=\mu(1)d(p)+\mu(p)d(1)=2-1=1f(1)=1,然后容易递推出 f(p^k)=1 进一步得出 f(p_1^{\alpha_1}p_2^{\alpha_2}\ldots p_k^{\alpha_k})=1。因此直接输出 1 即可。

Subtask 2

注意到 g(1)=0,根据定义可以计算出 f(1)=0,又因为 f 是积性函数,所以有 f(k)=f(1)f(k)=0,输出 0 即可。

Subtask 0

注意到 g(n)=\epsilon(n)=[n=1],此时有经典结论,\sum\limits_{d\mid n}\mu(d)\epsilon(\frac nd)=\mu(n),直接暴力算 \mu(n) 即可。

Subtask 3

注意到 n 的范围只有 10^5,然后题目还把 g(n) 的前 10^5 项的值全部给出了,因此想到直接上 Mobius 反演,得到 f(n)=\sum\limits_{d\mid n}\mu(\frac nd)g(d) 可以线性筛出 \mu 然后直接调和计数暴力算,做到 O(n\log n) 求解。

Subtask 5

把给出的 g 项暴力反演之后,注意到有 f(p^k)=p^{2k-1},所以直接暴力分解 n 的质因数,然后对每一项分别算答案即可。

Subtask 8

什么叫只给了 g(2)=2,g(3)=1,g(4)=3,g(7)=2()

先反演一下,可以得到 f(2)=1,f(3)=0,f(4)=1,f(6)=0,f(7)=1,然后根据定义可以得到 f(1)=1

直接大胆猜测 f(n)=n^2\bmod 3,交上去发现过了。

其实可以构造出其他满足条件的 f 的,这里给出一个例子:

Subtask 6

注意到此时有 g(n)=n^2,也就是有 f(n)=\sum\limits_{d\mid n}\mu(d)(\frac nd)^2=n^2\sum\limits_{d\mid n}\mu(d)\frac1{d^2}

因为 \muh(n)=\frac1{n^2} 都是积性函数,因此 \muh 的狄利克雷卷积 f(n) 也同样是积性函数,此时只需要计算出 f(p) 的值即可。

考虑从 g 入手推出 f 的式子,此时有 g(p^k)=(p^k)^2=p^{2k}=\sum\limits_{i=0}^kf(p^i),移项可得 f(p^i)=p^{2k}-\sum\limits_{i=0}^{k-1}f(p^i)=p^{2k}-g(p^{k-1})=p^{2k}-p^{2k-2}

根据积性函数的性质可知 f(p_1^{\alpha_1}p_2^{\alpha_2}\ldots p_k^{\alpha_k})=f(p_1^{\alpha_1})f(p_2^{\alpha_2})\ldots f(p_k^{\alpha_k})=(p^{2\alpha_1}-p^{2\alpha_1-2})(p^{2\alpha_2}-p^{2\alpha_2-2})\ldots(p^{2\alpha_k}-p^{2\alpha_k-2}),使用 Pollard-rho 分解质因数然后直接算即可,时间复杂度为 O(Tn^{\frac14})

Subtask 4

反演一次什么信息也得不到,于是考虑二次反演。注意到此时二次反演得到的函数 h(x) 满足 h(x)=\varphi(x)^2,于是考虑反演倒推出 f,有:f(x)=\sum\limits_{d\mid n}h(d)=\sum\limits_{d\mid n}\varphi^2(d)

Subtask 7

注意到题目上给出的提示叫 Poly,因此大胆猜想 f(p^k) 是一个多项式函数。把 f(p^k) 项全部反演解出来,可以直接 Gauss 消元直接解出 f(p^k) 对应的多项式。

最终可以解出 f(p^k)=114(p^k)^3+514(p^k)^2+1919(p^k)+810,然后利用 f 的积性可以推出任意一项 f(x) 的值。

062. P10665 [AMPPZ2013] Bytehattan

板子题,考虑平面图转对偶图,然后两个点连通的充要条件就是连接这两个点的边两侧在对偶图上对应的两个点不连通,一次断边操作就可以理解为是把这条边左右两侧在对偶图上对应的两个点连通起来,显然可以拿 dsu 简单实现。

一个细节是根据对偶图的定义,所有格点之外的区域也应该被整体单独编号。

总时间复杂度为 O(\alpha nm),可以轻松通过。

063. P7974 [KSN2021] Delivering Balls

1hr AK 了代码源 B 队选拔赛,感觉很不牛,咋有一堆我做过的原啊??

先交换 l,r 使得 l\le r

此时想要从 l 移动到 r,找到区间中最高的位置 id,高度为 h_{id},则显然在 id 这个时刻移动到恰好 h_{id} 高度是最优的,而在每个位置往上走的花费都是定值 4,因此考虑在最开始就移动到 h_{id} 这个高度,花费的代价就是 4(\max(h_i-i)-(h_l-l)),维护 a_i-i 的静态区间最值即可。

对称的有往下走的情况,同样维护一个 a_i+i 的静态区间最值即可。

前面计算的全都是直着上升的部分,还有斜着上升的段,斜着下降的段以及横着走的段,都是容易计算的。把上面所有东西加起来之后化简可以得到答案为:

最后的答案即为:mx1+2mx2+2mx3-4h_s-h_t(注意最后是 s,t 不是 l,r)。用三个 ST 表分别维护这三个区间最大值,总时间复杂度为 O(n\log n+Q),可以通过该题。

064. P14415 [JOISC 2015] 遗产继承 / Inheritance

从某个 OJ 上 copy 过来一个简要题意:

给一个 n 个节点 m 条边的无向图,有 k 轮操作,每轮操作选择尽量多的边删除。如果有多种方案,那么选择边权和最大的那个,但是要求删除的边中不存在环。

对于每条边,输出它在第几次操作被删除,如果这条边最后都没有被删除那么输出 0

注意到 n,k 的值都很小,因此考虑直接模拟每一次操作,容易想到每次操作删掉的边一定是在上一次保留下来的所有边中得到的最大生成森林。直接 Kruskal 暴力求可以做到 O(\alpha mk)(最初统一把边按边权排序然后懒惰删除)。

考虑优化。容易想到每条边肯定尽可能被早删除会更好,然后又注意到这个东西满足单调性可以二分,找到第一个加入该边不会成环的并查集。考虑维护 k 个并查集,表示删完前 i 次边之后图的连通情况。对所有边按照边权从大到小排序后,对于一条边 u,v 假设当前二分到的时刻为 mid,那么因为将连通性的艾弗森括号看做表达式后这个东西是单调的(显然),所以若 mid 时刻 u,v 两个点连通,那么在 1\sim mid-1 时刻两个点也一定是连通的,那么就向右半段查找,否则向左半段查找。

此时时间复杂度优化到 O(\alpha m\log k),可以通过该题。

065. P14300 [JOI2023 预选赛 R2] 货物列车 / Freight Train

先观察题目的性质,可以发现:

继续观察还可以发现:除了走的距离最近的那一次以外,其余每一次拿货物都把车厢拉满一定是最优的。

于是可以考虑 dp。设 f_{i,j,k} 表示当前考虑了 i\sim n 这个后缀内的物品,一共拿了 j 个(在模 W 意义下,特殊的把 0 看做是 W),走了 k 的路程,可得到的最大价值是多少。此时转移方程形如:

特殊的,需要特判掉拿了恰好 1 个物品时的情况:

直接做时空复杂度都是 O(n^4) 的(注意总距离是 O(n^2) 级别的)。空间是容易优化的,注意到 f_i 的信息只会从 f_{i+1} 递推过来所以可以把 i 这个维度滚动掉。

但是仍然难以优化时间复杂度。有一些题解里说可以使用决策单调性优化到三次方 \log 但是我已经把决策单调性忘干净了 所以(查看了题解之后)考虑使用另一种方法优化。

此时可以注意到在最坏情况下只需要走 O(\frac{n^2}W) 的路程就可以取走所有的物品,因此 d>O(\frac{n^2}W) 时直接输出 \sum a_i 即可。

而对于剩下的情况必然有 d<O(\frac{n^2}W),此时再跑暴力 dp 时间复杂度就降到 O(n^3) 的了,可以通过该题。这也太牛了!