杂题选谈
2025-11-04

这么爱计数


A. 图 / HDU4903 The only survival

https://vjudge.net/problem/HDU-4903#author=DeepSeek_zh

  • 很容易想到基于按 dis 从小到大枚举的做法

    但是发现算方案就必须要知道每个点的具体 dis,就导致难以 DP,只能搜索,这样复杂度就不太好看。

  • 一个显然的观察:并不关心 \(1,n\) 以外点的标号,所以可以把 \(O(n^k)\) 的暴搜优化到 \(O(\binom{n+k}k\cdot (n+k))\),然后做多重集排列即可。

    模数非质时的多重集排列:link

#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("graph.in", "r", stdin);
    std::freopen("graph.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, k, L, mod;
    std::cin >> n >> k >> L >> mod;
    if (L < k) {
        std::cout << 0 << '\n';
        return 0;
    }
    std::vector<std::vector<long long> > C(n + 1, std::vector<long long> (n + 1));
    C[0][0] = 1ll;
    for (int i = 1; i <= n; ++i) {
        C[i][0] = 1ll;
        for (int j = 1; j <= i; ++j)
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
    }
    std::vector<int> dis(n + 1), s;
    int res = 0;
    std::function<void(int, int, long long)> DFS = [&](int x, int d, long long now) {
        if (x == n) {
            auto s0 = now, s1 = now;
            for (int i = 1; i < n; ++i)
                if (dis[i] >= k)
                    (s0 *= L) %= mod, (s1 *= L) %= mod;
                else if (L - (k - dis[i] - 1)) {
                    (s0 *= L - (k - dis[i] - 1)) %= mod;
                    (s1 *= L - (k - dis[i] - 1) - 1) %= mod;
                }
                else {
                    s0 = s1 = 0ll;
                    break;
                }
            dis[x] = k;
            auto u = 1ll;
            int cnt = n - 2;
            for (auto i : s)
                (u *= C[cnt][i]) %= mod, cnt -= i;
            (res += u * (s0 + mod - s1) % mod) %= mod;
            return;
        }
        for (int i = d; i <= k + 1; ++i) {
            auto s0 = now, s1 = now;
            for (int j = 1; j < x; ++j)
                if (dis[j] >= i)
                    (s0 *= L) %= mod, (s1 *= L) %= mod;
                else if (L - (i - dis[j] - 1)) {
                    (s0 *= L - (i - dis[j] - 1)) %= mod;
                    (s1 *= L - (i - dis[j] - 1) - 1) %= mod;
                }
                else {
                    s0 = s1 = 0ll;
                    break;
                }
            dis[x] = i;
            if (dis[x] != dis[x - 1])
                s.push_back(1);
            else
                ++s.back();
            if (i == k + 1)
                s1 = 0ll;
            DFS(x + 1, i, (s0 + mod - s1) % mod);
            if (dis[x] != dis[x - 1])
                s.pop_back();
            else
                --s.back();
        }
        return;
    };
    DFS(2, 1, 1);
    std::cout << res % mod << '\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. 路线 / ARC136E Non-coprime DAG

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

  • 做过 CF870F Paths 可以很快反应过来:\(x\)\(y\) 是否可以同时被选,取决于它们各自的最小质因子是否能在 \(x,y\) 之间交汇(或用 \(2\) 作跳板)。

    接着就发现由于 \(2\) 间隔出现,此时再分奇偶性就会显得非常合理。

  • 两个偶数总是不能同时被选;对于奇数 \(x\) 和偶数 \(y\),要求 \(y\in [x-f(x)+1,x+f(x)-1]\)

  • 考察奇数的选取。容易发现,钦定用 \(2\) 作跳板,则两个奇数 \(x,y(x<y)\) 能同时被选,当且仅当:

    • \(f(i)\)\(i\) 的最小质因子,则 \(x+f(x)> y-f(y)\)

    发现实际上可以认为 \(x\) 代表区间 \([x-f(x),x+f(x)-1]\)。那么两个点可以同时被选当且仅当它们代表的区间有交(这样就去掉了 \(x,y\) 之间的偏序条件)

    这样发现对『代表区间』的定义,在奇数视角和偶数视角下是冲突的,可以发现偶数视角的区间更紧;事实上,应该采用 \([x-f(x)+1,x+f(x)-1]\) 这个看似充分不必要的定义,因为端点总是奇数,导致 \(x+f(x)> y-f(y)\)\(x+f(x)-1<y-f(y)+1\) 不能同时成立。

  • 进一步推广结论,容易发现多个奇数可以同时被选,当且仅当它们代表的区间有交。故可以枚举值域中的点,找加权覆盖次数最大值。

#include <bits/stdc++.h>
using namespace std;
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 n;
    std::cin >> n;
    std::vector<int> fac(n + 1), l(n + 1), r(n + 1);
    for (int i = 2; i <= n; ++i) {
        if (!fac[i]) {
            fac[i] = i;
            for (int j = 2 * i; j <= n; j += i)
                if (!fac[j])
                   fac[j] = i;
        }
        l[i] = std::max(1, i - fac[i] + 1);
        r[i] = std::min(i + fac[i] - 1, n);
    }
    std::vector<int> a(n + 1);
    std::vector<long long> dif(n + 1);
    for (int i = 1; i <= n; ++i) {
        std::cin >> a[i];
        if (i == 1 || i % 2 == 0)
            continue;
        dif[l[i]] += a[i];
        if (r[i] != n)
            dif[r[i] + 1] -= a[i];
    }
    auto res = 0ll;
    std::partial_sum(dif.begin(), dif.end(), dif.begin());
    for (int i = 1; i <= n; ++i) {
        auto now = a[1] + dif[i];
        if (i % 2 == 0)
            now += a[i];
        res = std::max(res, now);
    }
    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;
}

A. 列车扫除

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

绝对聪明的 A, B, C 在一起玩 Nim,有三堆石子 \(c_{1,2,3}\),每次可以任选一堆拿走正整数个,不能拿的人扣一分,他的上一个人加一分。

给定 \(q\) 次询问,形如:

  • 现在知道 \(\forall \, i=1,2,3,c_i\in[l_i,r_i]\)。对于这 \(\prod_{i=1,2,3}r_i-l_i+1\) 种可能的情况,对于每个人,求出分数之和。

\(q\le 10^6,V=10^9\)

  • 容易发现,胜,平,负三个状态会被分给三个人,且一共只有三种分配方式。

  • 当只剩一堆石头时,操作者胜;石头状态为 \((0,1,1)\) 时,操作者负。


B. 换来换去

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

\(n\) 个有标号的球任意分成任意组,组是无顺序的,且要求每组球个数 \(\ge 2\),求方案数,对质数取模。

\(n\le 10^7\)

  • 发现这是一个类斯特林数的问题,二项式反演得到答案式为:

    \[ \sum_{i=0}^n (-1)^{n-i}\binom ni \sum_{j=0}^i \begin{Bmatrix} i\\ j\end{Bmatrix} \]

    用斯特林数通项展开:

    \[ \sum_{i=0}^n (-1)^{n-i}\binom ni \sum_{j=0}^i \sum_{k=0}^j(-1)^{j-k}\dfrac {k^i}{(j-k)!\cdot k!} \]

    很容易注意到一个二项式定理的结构,故交换求和顺序:

    \[ \sum_{j=0}^n \sum_{k=0}^j \dfrac {(-1)^{j-k}}{(j-k)!\cdot k!}\cdot \sum_{i=j}^n \binom ni (-1)^{n-i}k^i \]

    发现一个很严重的问题在于 \(i\) 的起始范围是 \(j\) 而不是 \(0\),但如果我们把一开始的式子改写成这样:

    \[ \sum_{i=0}^n (-1)^{n-i}\binom ni \sum_{j=0}^n \begin{Bmatrix} i\\ j\end{Bmatrix} \]

    容易发现当 \(j>i\)\(\begin{Bmatrix} i\\ j\end{Bmatrix}=0\),和原式的值相同,且斯特林数通项对于 \(j>i\) 也是成立的,故原式等价于

    \[ \begin{aligned} &\sum_{j=0}^n \sum_{k=0}^j \dfrac {(-1)^{j-k}}{(j-k)!\cdot k!}\cdot \sum_{i=0}^n \binom ni (-1)^{n-i}k^i\\ =&\sum_{j=0}^n \sum_{k=0}^j \dfrac {(-1)^{j-k}}{(j-k)!\cdot k!}\cdot (k-1)^n\\ =&\sum_{k=0}^n \dfrac {(k-1)^n}{k!}\cdot \sum_{j=0}^{n-k} \dfrac {(-1)^j}{j!}\\ \end{aligned} \]

    后者内部与 \(k\) 无关,是可前缀和计算的,只需要考虑在 \(O(1)\) 内求出 \((k-1)^n\) 的值,筛一下,对于质数(约 \(\dfrac n{\ln 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("card.in", "r", stdin);
    std::freopen("card.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, mod;
        std::cin >> n >> mod;
        std::vector<int> tag(n + 1), p, pw(n + 1);
        auto qkp = [&](long long x, int y) {
            auto res = 1ll;
            for (; y; (x *= x) %= mod, y >>= 1)
                if (y & 1)
                    (res *= x) %= mod;
            return res;
        };
        pw[1] = 1ll;
        for (int i = 2; i <= n; ++i) {
            if (!tag[i]) {
                pw[i] = qkp(i, n);
                p.push_back(i);
            }
            for (auto j : p) {
                if (i > n / j)
                    break;
                tag[i * j] = 1;
                pw[i * j] = (long long)pw[i] * pw[j] % mod;
                if (i % j == 0)
                    break;
            }
        }
        std::vector<int> inv(n + 1), s(n + 1);
        inv[0] = 1ll;
        for (int j = 1; j <= n; ++j)
            inv[j] = (long long)inv[j - 1] * j % mod;
        inv[n] = qkp(inv[n], mod - 2);
        for (int j = n - 1; j; --j)
            inv[j] = (long long)inv[j + 1] * (j + 1) % mod;
        s[0] = inv[0];
        for (int j = 1; j <= n; ++j) {
            if (j & 1)
                s[j] = s[j - 1] + mod - inv[j];
            else
                s[j] = s[j - 1] + inv[j];
            if (s[j] >= mod)
                s[j] -= mod;
        }
        auto res = (long long)s[n] * ((n & 1) ? mod - 1 : 1);
        for (int k = 1; k <= n; ++k)
            res += (long long)pw[k - 1] * inv[k] % mod * s[n - k] % mod;
        std::cout << res % mod << '\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. 画家

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

  • ARC 特供删十字,故时光倒流,转化成在图上删相同颜色的十字 / 行 / 列。

  • \(f_{i,j}\) 表示合法的 \(i\)\(j\) 列地图。发现同颜色的删行 + 删列会和直接删十字算重,


B. 灯光秀 / CF1545C AquaMoon and Permutations

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

  • 第一步需要想到,如果某一列的某个数,只有一个排列有,那么这个排列必须被选入拉丁方;

    用这个必选的排列,可以排除掉一些与之冲突、不能选入拉丁方的排列。


A. bot 的能量堆

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

超越一切震慑凡人

给定正整数 \(x,y\),你可以执行下面三种操作:

  1. \(x,y\) 同时加 1;
  2. \(x,y\) 同时减一;
  3. 对于 \(\gcd(x,y)\) 的一个质因子 \(p\),将 \(x,y\) 同时除以 \(p\)

问最少花费多少次操作使得 \(\min(x,y)=1\)。多测。

\(T\le 300,1\le x,y\le 10^9\)

  • 不妨先考虑 \(x\ne y\) 的情况,发现三种操作都不会使 \(x,y\) 的相对大小关系改变。故令 \(x<y\),考虑让 \(x\) 变为 1。

  • \(d=y-x\), 很容易注意到 \(d\) 的值不会在前两种操作中改变,由辗转相减,\(\gcd(x,y)=\gcd(d, x)\),即操作三每次选取的 \(p\) 总是 \(d\) 的质因子,且总能通过若干次操作 1/2 让 \(p\) 能够执行。

    每次执行操作 3 后,\(d\gets d\div p\),每次只需让 \(x\) 变为 \(\lfloor \frac xp\rfloor\)\(\lceil \frac xp\rceil\)

    直接记搜的话,容易发现状态总数是 V 因数总数。

  • 对于 \(x=y\) 的情况,答案至多为 \(x\) 的质因子数量。暴搜 + 剪枝非常可过。

    有一种神秘的处理方式…

    if (x == y) {
        auto calc = [&](int x) {
            int cnt = 0;
            for (int i = 2; i * i <= x; ++i)
                for (; x % i == 0; ++cnt, x /= i);
            return cnt + (x != 1);
        };
        int to = calc(x), res = to;
        for (int i = std::max(x - to, 1); i <= x + to; ++i)
            res = std::min(res, calc(i) + std::abs(i - x));
        std::cout << x << '\n';
        continue;
    }

    容易发现这东西没什么道理,反例大概率存在但在小范围内确实难以构造。总之数据没卡到。


B. bot 的矩阵

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

⚡带来终结机械降神⚡

有一个 \(n\times n\) 的二维数组,初始只知道 \(m\) 个位置的数 \(a_{x,y}\),以及每行、每列的元素和 \(sx_{1\cdots n},sy_{1\cdots n}\)

构造出一个合法的解,每个数在 \([-2^63,2^63)\) 内。

\(n\le 2000\),\(a_{x,y},sx_i,sy_i\in [-10^9,10^9]\)\(m\le n^2\)

  • 听说很容易想到二分图,但实在反应不过来。但在乱填的时候发现,如果出现了『必填』的情况,是一个行列交错的链式反应,这样就很容易想到二分图了

    原理是同时影响行 \(i\)\(j\) 的元素只有 \((i, j)\)

  • 相当于给一个 \(2n\) 个点 \(n^2\) 条边的二分图,其中一些边权是已知的,那么不妨认为这些边被删除了

    同时也是一个 \(n^2\) 个元,\(2n-1\) 个方程的方程组(\(sx=sy\) 会消掉一条方程);故很多元其实可以直接赋 \(0\),只拿 \(2n-1\) 个元出来解方程。

  • 在挖掉已知边的二分图上任意找生成树(森林)就可以满足 \(2n-1\) 的限制,结合树上高斯消元,从叶子开始解方程即可。

#include <bits/stdc++.h>
#define int long long
#define nec getchar
void read(int &x) {
    x = 0;
    char ch = nec();
    bool flag = false;
    for (; ch < '0' || ch > '9'; ch = nec())
        if (ch == '-')
            flag = true;
    for (; ch >= '0' && ch <= '9'; ch = nec())
        x = x * 10 + ch - '0';
    if (flag)
        x = -x;
    return;
}
signed main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen("matrix3.in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    int T;
    for (read(T); T--; ) {
        int n, m;
        read(n), read(m);
        std::vector<int> sx(n + 1), sy(n + 1);
        for (int i = 1; i <= n; ++i)
            read(sx[i]);
        for (int i = 1; i <= n; ++i)
            read(sy[i]);
        std::vector<std::vector<int> > vis(n + 1, std::vector<int> (n + 1));
        std::vector<std::vector<long long> > a(n + 1, std::vector<long long> (n + 1));
        std::vector<int> tx(n + 1), ty(n + 1);
        auto work = [&](int i, int j, int c) {
            vis[i][j] = 1;
            ++tx[i], ++ty[j];
            sx[i] -= c, sy[j] -= c, a[i][j] = c;
            return;
        };
        for (int x, y, c; m--; ) {
            read(x), read(y), read(c);
            work(x, y, c);
        }
        if (std::accumulate(sx.begin() + 1, sx.end(), 0ll) != std::accumulate(sy.begin() + 1, sy.end(), 0ll)) {
            std::cout << "NoSolution!" << '\n';
            continue;
        }
        std::vector<int> fx(n + 1), fy(n + 1);
        std::function<bool(void)> check = [&](void) {
            for (int x = 1; x <= n; ++x)
                if (!fx[x]) {
                    if (tx[x] == n) {
                        if (sx[x] != 0)
                            return false;
                        fx[x] = 1;
                        if (!check())
                            return false;
                    }
                    else if (tx[x] == n - 1) {
                        for (int y = 1; y <= n; ++y)
                            if (!vis[x][y]) {
                                work(x, y, sx[x]);
                                fx[x] = 1;
                                if (!check())
                                    return false;
                            }
                    }
                }
            for (int y = 1; y <= n; ++y)
                if (!fy[y]) {
                    if (ty[y] == n) {
                        if (sy[y] != 0)
                            return false;
                        fy[y] = 1;
                        if (!check())
                            return false;
                    }
                    else if (ty[y] == n - 1) {
                        for (int x = 1; x <= n; ++x)
                            if (!vis[x][y]) {
                                work(x, y, sy[y]);
                                fy[y] = 1;
                                if (!check())
                                    return false;
                            }
                    }
                }
            return true;
        };
        if (!check()) {
            std::cout << "NoSolution!" << '\n';
            continue;
        }
        std::function<void(int, int)> DFS = [&](int x, int op) {
            if (op == 0) {
                fx[x] = 1;
                for (int i = 1; i <= n; ++i)
                    if (!vis[x][i] && !fy[i]) {
                        DFS(i, 1);
                        work(x, i, sy[i]);
                    }
            }
            else {
                fy[x] = 1;
                for (int i = 1; i <= n; ++i)
                    if (!vis[i][x] && !fx[i]) {
                        DFS(i, 0);
                        work(i, x, sx[i]);
                    }
            }
            return;
        };
        for (int i = 1; i <= n; ++i)
            if (!fx[i])
                DFS(i, 0);
        for (int i = 1; i <= n; ++i)
            if (!fy[i])
                DFS(i, 1);
        std::cout << "Botany!" << '\n';
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= n; ++j)
                std::cout << a[i][j] << ' ';
            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;
}

A. 数码串

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

给定一个长度为 \(n\) 的数字串,现在需要给它分为若干段,问在所有 \(2^{n-1}\) 种分段方式中,有多少种满足:

  • 在任意相邻的两段中,有至少一段,其对应的十进制数是 \(D\) 的倍数。

答案对 \(10^9+7\) 取模。

多测,\(n\le 2\times 10^5,D\le 10^6,T\le 100\)

部分分特殊性质\(\gcd(D,10)=1\)

  • 发现若 \(a_{l\cdots r}\)\(D\) 的倍数,记 \(s_i=s_{i+1}+a_i\times 10^{n-i}\),那么 \(\dfrac{s_l-s_{r+1}}{10^{n-r}}\bmod D=0\)

    题目要求,这个转移点和上个转移点,至少有一个满足该式。显然可以类 DP 地做。

  • 对于 \(D\)\(10\) 互质的情况,原条件等价于 \(s_l\equiv s_{r+1}\pmod D\),用一个桶记录 DP 值即可

  • \(D=2^*\times 5^?\times m\),发现 \(m\)\(10\) 互质,或许可以套用上方的做法

    由于 \(D\le 10^6 < 2^{20}\),对于一个固定的 \(r\),所有 \(l\le r-20\) 项的 \(a_l\)\(a_{l\cdots r}\bmod (2^*\times 5^?)\) 的贡献总是 \(0\),故在只考虑 \(2^*\times 5^?\) 时, \(l\le r-20\) 的可选性和 \(l=r-20\) 的可选性相同。只需要对于 \(\bmod m\) 沿用桶做法即可。

    对于 \(l\ge r-20\),暴力即可,复杂度 \(O(T(n+D))\)

注意 \(s_l-s_{r+1}\) 里的 \(r+1\),调成鸲了

#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("digit.in", "r", stdin);
    std::freopen("digit.out", "w", stdout);
#else
    std::freopen("./test/20251113/digit/ex_digit1.in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    int T;
    std::vector<std::array<long long, 2> > c(1e6);
    for (std::cin >> T; T--; ) {
        std::string a;
        int d, n, m, p;
        std::cin >> a >> d, n = (int)a.length();
        a = "#" + a;
        std::vector<std::vector<long long> > f(n + 1, std::vector<long long> (2));
        f[0][0] = 1ll;
        m = d;
        for (; m % 2 == 0; m /= 2);
        for (; m % 5 == 0; m /= 5);
        p = d / m;
        for (int i = 0; i < m; ++i)
            c[i][0] = c[i][1] = 0ll;
        std::vector<int> s(n + 2);
        for (int i = n, k = 1; i; --i, (k *= 10) %= m)
            s[i] = (s[i + 1] + (a[i] - '0') * k) % m;
        auto s0 = 0ll;
        for (int i = 1; i <= n; ++i) {
            int now = 0;
            for (int j = i, k = 1; j > i - 20 && j; --j, (k *= 10) %= d) {
                (now += (a[j] - '0') * k) %= d;
                if (now == 0)
                    (f[i][0] += f[j - 1][0] + f[j - 1][1]) %= mod;
                else
                    (f[i][1] += f[j - 1][0]) %= mod;
            }
            if (i > 20) {
                (c[s[i - 20]][0] += f[i - 21][0]) %= mod;
                (c[s[i - 20]][1] += f[i - 21][1]) %= mod;
                (s0 += f[i - 21][0]) %= mod;
                int now = 0;
                for (int j = i, k = 1; j >= i - 20; --j, (k *= 10) %= p)
                    (now += (a[j] - '0') * k) %= p;
                if (now == 0) {
                    (f[i][0] += c[s[i + 1]][0] + c[s[i + 1]][1]) %= mod;
                    (f[i][1] += s0 + mod - c[s[i + 1]][0]) %= mod;
                }
                else
                    (f[i][1] += s0) %= mod;
            }
        }
        std::cout << (f[n][0] + f[n][1]) % mod << '\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. 背包

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

给定 \(n\) 种物品,第 \(i(1\le i\le n)\) 种有无穷多个,体积均为 \(v_i\)、价值均为 \(w_i\)。给定 \(Q\) 次询问,形如:

  • 给定一个背包容积 \(m\),回答两个问题:

    1. 有序地选取体积和恰好\(m\) 的物品,所能得到的最大价值。如果不存在这样的选取方式,回答 \(-1\)
    2. 在上一问的条件下,可能的方案数。两个方案不同,当且仅当选取的物品数不同,或选物序列的某个位置不同。对 \(1092515507\) 取模,不存在则回答 \(-1\)

\(n,Q,v_i\le 100, m,w_i\le 10^9\)

mobai ddxrS

  • Tip:由于选取是有序的,所以朴素的『枚举物品 + 枚举体积』的 DP 是不可行的,而需要『枚举体积 + 枚举物品』。

  • 很容易写出朴素的 DP:令二元组 \((a, b)_{m}\) 表示总体积 \(m\) 下的两问答案,则合并答案(定义为 \(+\) 运算)的过程可以写为:

    \[ (a_1,b_1)_{m}+(a_2,b_2)_{m}=\begin{cases} (a_1,b_1)_{m}&a_1>a_2\\ (a_1,b_1+b_2)_{m}&a_1=a_2\\ (a_2, b_2)_{m}&\text{otherwise} \end{cases} \]

  • 相似地,状态转移(定义为 \(\times\) 运算)可以写为:

    \[ (a_1,b_1)_{m_1}\times (a_2,b_2)_{m_2}=(a_1+a_2,b_1\times b_2)_{m_1+m_2} \]

    Tips:实际上是借鉴了矩阵乘法的含义进行定义,这样就可以尝试优化。

  • 这样就有 \((f,g)_i = \sum\limits_{j=1}^n (f,g)_{i-v_j}\times (w_j,1)_{v_j}\)

  • 感性理解:显然,在直接背包时,\(+\) 运算与 \(\times\) 运算具有交换律,且 \(\times\)\(+\) 有分配律。

    那么此时广义矩乘具有结合律,可以优化。

  • 体积最大为 \(100\),考虑开 \(100\times 100\) 的矩阵(元素为二元组)维护 \((f,g)_{i-99}\sim (f,g)_i\)。考虑直接快速幂,发现复杂度是非常糟糕的 \(O(qv^3\log m)\),非常糟糕。

    初始矩阵是 \(1\times 100\) 的,这里的处理方式是预处理每一个 \(2^i\) 次幂(\(O(v^3\log m)\)),每次询问时二进制拆分 \(m\),线性地乘过去,就可以把矩阵乘法优化为矩阵乘向量,复杂度降至 \(O(qv^2\log m)\)

#include <bits/stdc++.h>
constexpr int N = 100;
const long long inf = 1e18;
const int mod = 1092515507;
struct node {
    long long f, g;
    node(): f(-inf), g(0ll) {}
    node(long long f1, long long g1): f(f1), g(g1) {
        if (g >= mod)
            g -= mod;
        return;
    }
    node operator* (const node &q) const {
        return node(f + q.f, g * q.g % mod);
    }
    node operator+ (const node &q) const {
        if (f > q.f)
            return *this;
        else if (f == q.f)
            return node(f, g + q.g);
        return q;
    }
    node& operator+= (const node &q) {
        return *this = *this + q;
    }
};
struct mat {
    std::vector<std::vector<node> > a;
    mat(void): a(N, std::vector<node> (N)) {}
    std::vector<node>& operator[] (const int q) {
        return a[q];
    }
    mat operator* (mat &q) const {
        mat res;
        for (int i = 0; i < N; ++i)
            for (int k = 0; k < N; ++k)
                for (int j = 0; j < N; ++j) 
                    res[i][j] += a[i][k] * q[k][j];
        return res;
    }
};
struct vec {
    std::vector<node> a;
    vec(void): a(N) {}
    node& operator[] (const int q) {
        return a[q];
    }
    vec operator* (mat &q) const {
        vec res;
        for (int k = 0; k < N; ++k)
            for (int j = 0; j < N; ++j)
                res[j] += a[k] * q[k][j];
        return res;
    }
    vec& operator*= (mat &q) {
        return *this = *this * q;
    }
};
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
    std::freopen("pack.in", "r", stdin);
    std::freopen("pack.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, q;
    std::cin >> n >> q;
    std::vector<int> v(n + 1), w(n + 1);
    for (int i = 1; i <= n; ++i)
        std::cin >> v[i];
    for (int i = 1; i <= n; ++i)
        std::cin >> w[i];
    std::vector<mat> t(30);
    for (int j = 0; j < N - 1; ++j)
        t[0][j + 1][j] += node(0ll, 1ll);
    for (int i = 1; i <= n; ++i)
        t[0][N - v[i]][N - 1] += node(w[i], 1ll);
    for (int i = 1; i < 30; ++i)
        t[i] = t[i - 1] * t[i - 1];
    vec init;
    init[N - 1] = node(0ll, 1ll);
    for (int m; q--; ) {
        std::cin >> m;
        auto res(init);
        for (int i = 29; ~i; --i)
            if ((m >> i) & 1)
                res *= t[i];
        if (res[N - 1].f <= 0)
            std::cout << -1 << ' ' << -1 << '\n';
        else
            std::cout << res[N - 1].f << ' ' << res[N - 1].g << '\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;
}

A. 关卡设计

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

给定 \(n\times m\) 的字符地图,其中 S 为起点(有且仅有一个),RB 为红、蓝宝石(可能有多个),x 为障碍、. 为可通行地块。

现在需要把尽可能多的 . 变为 x,使得一种原来可以由 S 出发收集到的宝石在修改后的地图中仍能被收集。输出最多可修改的 . 的数量。

\(n\times m\le 10^6\)

  • 一种显然错误的做法:拆点 \((u,0/1/2/3)\) 分别表示走到 \(u\) 时的宝石收集状态,跑 01BFS。

    错误的原因:路径会重复走一些边,但它们不能被重复记入代价。

  • 结合『重复走一些边』的想法,发现路径一定有一个『分叉点』。枚举这样的分叉点,从 S、所有 R、所有 B 出发分别跑 01BFS。

#include <bits/stdc++.h>
const int dir[][2] = { { 0, -1 }, { 0, 1 }, { -1, 0 }, { 1, 0 } };
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
    std::freopen("design.out", "w", stdout);
    std::freopen("design.in", "r", stdin);
#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, m,  cnt = 0;
        std::cin >> n >> m;
        std::vector<std::vector<char> > a(n + 1, std::vector<char> (m + 1));
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= m; ++j) {
                std::cin >> a[i][j];
                cnt += (a[i][j] == '.');
            }
        const int N = n * m;
        auto id = [&](int i, int j) { return (i - 1) * m + j; };
        std::vector<std::vector<int> > dis(3, std::vector<int> (N + 1, N + 1));
        auto work = [&](std::vector<int> &dis, char S) {
            std::deque<int> q;
            std::vector<int> vis(N + 1);
            for (int i = 1; i <= n; ++i)
                for (int j = 1; j <= m; ++j)
                    if (a[i][j] == S) {
                        dis[id(i, j)] = 0;
                        q.push_back(id(i, j));
                    }
            for (; !q.empty(); ) {
                int x, y;
                {
                    int t = q.front();
                    q.pop_front();
                    if (vis[t])
                        continue;
                    vis[t] = 1;
                    x = (t - 1) / m + 1, y = t - (x - 1) * m;
                }
                for (auto [fx, fy] : dir) {
                    int nx = x + fx, ny = y + fy;
                    if (nx >= 1 && ny >= 1 && nx <= n && ny <= m && a[nx][ny] != 'x') {
                        if (a[nx][ny] != '.') {
                            if (dis[id(nx, ny)] > dis[id(x, y)]) {
                                dis[id(nx, ny)] = dis[id(x, y)];
                                q.push_front(id(nx, ny));
                            }
                        }
                        else if (dis[id(nx, ny)] > dis[id(x, y)] + 1) {
                            dis[id(nx, ny)] = dis[id(x, y)] + 1;
                            q.push_back(id(nx, ny));
                        }
                    }
                }
            }
        };
        work(dis[0], 'S'), work(dis[1], 'R'), work(dis[2], 'B');
        int mn1 = N + 1, mn2 = N + 1;
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= m; ++j) {
                mn1 = std::min(mn1, dis[0][id(i, j)] + std::min(dis[1][id(i, j)], dis[2][id(i, j)]) - (a[i][j] == '.'));
                mn2 = std::min(mn2, dis[0][id(i, j)] + dis[1][id(i, j)] + dis[2][id(i, j)] - (a[i][j] == '.') * 2);
            }
        if (mn2 != N + 1)
            std::cout << cnt - mn2 << '\n';
        else if (mn1 != N + 1)
            std::cout << cnt - mn1 << '\n';
        else
            std::cout << cnt << '\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. 炼金术

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

给定 \(A_{1\cdots n}\)\(B_{1\cdots n}\),给定 \(m\) 次操作,形如:

  • 1 l r x,将 \(A_{l\cdots r}\) 赋为 \(x\)
  • 2 l r,询问 \(\min\limits_{i\in [l, r]}\left\{ \text{lcm}(A_i,B_i)+\dfrac{\text{lcm}(A_i,B_i)}{\gcd(A_i,B_i)} \right\}\) 的值。

\(n,m\le 5\times 10^4,V=5\times 10^4\)

  • 待求没什么太好的性质,考虑更暴力的解决方式。

    容易想到分块做法,对于整块赋值,容易想到对于每个 \(x\) 预处理块内答案。如何快速做到这一点?

    一个想法是枚举 gcd 的值 \(p\)(显然只需枚举 \(x\) 的因子),计算 \(\dfrac {b_j\times x}p+\dfrac {b_j\times x}{p^2}\) 的最小值。

    Tip:枚举到的 \(p\) 可能不是 gcd,但能保证一定能找到答案。

  • 贪心地,只需对于块内 \(p\) 的最小倍数计算答案。这一点对于每个 \(b_i\) 枚举因数即能 \(O(n\sqrt V)\) 地预处理(因为有重复元素所以不是 \(O(V\log V)\))、花费 \(O(V\log n)\) 的空间存储。

  • 对于每个 \(x\),复杂度不允许暴力枚举 \(x\) 的因子;发现 \(x\) 的因子是 \(x\) 与『对于所有 \(x\) 的质因子 \(m\)\(\dfrac xm\) 的因子』的并。

    考虑一个类 DP 的过程,则 \(f_x\gets f_{x\div m}\times m\),再计算 \(x\) 自身的答案即可。由于 \(\omega(V)\) 平均为 \(\log\log V\),此时预处理复杂度降低至 \(O(n\sqrt V + V\log V\sqrt n)\)

  • 用一点科技加速 gcd 即可。可选 \(O(1)\) gcd,或小范围打表 + 大范围暴力跳至小范围。

注意待求可能会爆 unsigned int,问就是挂分了。

#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("alchemy.in", "r", stdin);
    std::freopen("alchemy.out", "w", stdout);
#else
    std::freopen("./test/20251115/alchemy/alchemy4.in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    const long long inf = 1e18;
    const int M = 5000, N = 50000;
    std::vector<std::vector<int> > g(M + 1, std::vector<int> (M + 1));
    for (int i = 0; i <= M; ++i) {
        for (int j = 0; j < i; ++j)
            g[i][j] = g[j][i];
        g[i][i] = i;
        for (int j = i + 1; j <= M; ++j)
            g[i][j] = std::__gcd(i, j);
    }
    std::function<int(int, int)> gcd = [&](int x, int y) {
        if (x <= M && y <= M)
            return g[x][y];
        return y ? gcd(y, x % y) : x;
    };
    std::vector<std::vector<int> > fac(N + 1);
    for (int i = 2; i <= N; ++i)
        if (fac[i].empty())
            for (int j = i; j <= N; j += i)
                fac[j].push_back(i);
    int n, m;
    std::cin >> n >> m;
    std::vector<long long> a(n + 1), b(n + 1), p(n + 1);
    auto calc = [&](long long x, long long y) {
        auto g = gcd(x, y);
        return x * y / g + x * y / g / g;
    };
    for (int i = 1; i <= n; ++i)
        std::cin >> a[i];
    for (int i = 1; i <= n; ++i) {
        std::cin >> b[i];
        p[i] = calc(a[i], b[i]);
    }
    int siz = sqrt(n), k = (n + siz - 1) / siz;
    std::vector<long long> u(k + 1, inf);
    std::vector<int> L(k + 1), R(k + 1), id(n + 1), d(k + 1, -1);
    std::vector<std::vector<long long> > mn(k + 1, std::vector<long long> (N + 1, inf)), s(k + 1, std::vector<long long> (N + 1));
    for (int i = 1; i <= k; ++i) {
        L[i] = R[i - 1] + 1, R[i] = std::min(L[i] + siz - 1, n);
        for (int j = L[i]; j <= R[i]; ++j) {
            id[j] = i, u[i] = std::min(u[i], p[j]);
            for (int k = 1; k * k <= b[j]; ++k)
                if (b[j] % k == 0) {
                    mn[i][k] = std::min(mn[i][k], b[j]);
                    mn[i][b[j] / k] = std::min(mn[i][b[j] / k], b[j]);
                }
        }
        s[i][1] = mn[i][1] * 2;
        for (int j = 2; j <= N; ++j) {
            s[i][j] = mn[i][j] + mn[i][j] / j;
            for (auto k : fac[j])
                s[i][j] = std::min(s[i][j], s[i][j / k] * k);
        }
    }
    auto pushdown = [&](int id) {
        if (d[id] != -1) {
            for (int i = L[id]; i <= R[id]; ++i)
                p[i] = calc(d[id], b[i]);
            d[id] = -1;
        }
        return;
    };
    auto ADD = [&](int l, int r, int x) {
        int pl = id[l], pr = id[r];
        pushdown(pl);
        if (pl == pr) {
            for (int i = l; i <= r; ++i)
                p[i] = calc(x, b[i]);
            u[pl] = *std::min_element(p.begin() + L[pl], p.begin() + R[pl] + 1);
            return;
        }
        pushdown(pr);
        for (int i = l; i <= R[pl]; ++i)
            p[i] = calc(x, b[i]);
        u[pl] = *std::min_element(p.begin() + L[pl], p.begin() + R[pl] + 1);
        for (int i = L[pr]; i <= r; ++i)
            p[i] = calc(x, b[i]);
        u[pr] = *std::min_element(p.begin() + L[pr], p.begin() + R[pr] + 1);
        for (int i = pl + 1; i < pr; ++i)
            d[i] = x, u[i] = s[i][x];
        return;
    };
    auto ASK = [&](int l, int r) {
        int pl = id[l], pr = id[r];
        pushdown(pl);
        if (pl == pr)
            return *std::min_element(p.begin() + l, p.begin() + r + 1);
        pushdown(pr);
        auto res = std::min(*std::min_element(p.begin() + l, p.begin() + R[pl] + 1), *std::min_element(p.begin() + L[pr], p.begin() + r + 1));
        if (pl + 1 != pr)
            res = std::min(res, *std::min_element(u.begin() + pl + 1, u.begin() + pr));
        return res;
    };
    for (int op, l, r; m--; ) {
        std::cin >> op >> l >> r;
        if (op == 1) {
            int x;
            std::cin >> x, ADD(l, r, x);
        }
        else
            std::cout << ASK(l, r) << '\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;
}

言论