Loading

[蓝桥杯 2020 国 B] 质数行者

前言

也是终于做到了 \(\rm{WBH}\) 大佬说的好题, 听过讲的情况下也是一点没听懂

思路

首先考虑暴力 \(\rm{dp}\)

显然的我们可以暴力做 \(\rm{dp}\) , 大概是 \(\mathcal{O} (n^3 \omega)\) 的, 其中 \(\omega\) 是质数个数
考虑优化

你注意到只有两个障碍点, 这个好像很重要
考虑「容斥原理」, 正难则反, 记 \(a \to b\) 的方案数为 \(f_{a, b}\) , 设起点和终点分别为 \(s, t\) , 两个特殊点分别为 \(u, v\)

\[ans = f_{s, t} - [ f_{s, u} \times f_{u, t} + f_{s, v} \times f_{v, t} - ( f_{s, u} \times f_{u, v} \times f_{v, t} + f_{s, v} \times f_{v, u} \times f_{u, t} ) ] \]

然后考虑 \(f\) 怎么求, 发现相当于走一个矩阵, 从左下到右上


因为直接 \(\rm{dp}\) 肯定不优, 考虑组合数学计算

首先我们发现, 这种问题很像之前的经典问题, 在之前的问题中

\[ans = {x + y + z \choose x}{y + z \choose y} \]

即在 \(x + y + z\) 次操作中挑选 \(x\) 次作为横向移动, 然后再剩下的 \(y + z\) 次操作中挑选 \(y\) 次作为纵向移动, 剩下的就不用计算组合数了

但是这个问题之中, 要求每次走的长度为质数, 怎么处理?

还是考虑操作次数中做组合数, 考虑预处理出 \(g_{i, j}\) 表示使用 \(i\) 个质数走 \(j\) 长度的方案数, 这个是 \(\mathcal{O} (n^2 \omega)\) 预处理即可

先考虑 \(g\) 的转移

\[g_{0, 0} = 1 \\ g_{i, j} = \sum_{p = 1}^{cnt} \left[ pri_p \leq j \right] \times g_{i - 1, j - pri_p} \]

考虑

\[f(x, y, z) = \sum_{i = 1}^{\lfloor \frac{x}{2} \rfloor}\sum_{j = 1}^{\lfloor \frac{y}{2} \rfloor}\sum_{k = 1}^{\lfloor \frac{z}{2} \rfloor} {i + j + k \choose i}{j + k \choose j} g_{i, x} \cdot g_{j, y} \cdot g_{k, z} \]

这个 \(\mathcal{O} (n^3)\) , 这不是只比直接 \(\rm{dp}\) 快一个 \(\omega\) ? 还要优化

这个多半只能对柿子进行优化, 考虑减少 \(\sum\) 的个数

拆!

\[\begin{align*} f(x, y, z) & =\sum_{i = 1}^{\lfloor \frac{x}{2} \rfloor}\sum_{j = 1}^{\lfloor \frac{y}{2} \rfloor}\sum_{k = 1}^{\lfloor \frac{z}{2} \rfloor} {i + j + k \choose i}{j + k \choose j} g_{i, x} \cdot g_{j, y} \cdot g_{k, z} \\ & = \sum_{i = 1}^{\lfloor \frac{x}{2} \rfloor}\sum_{j = 1}^{\lfloor \frac{y}{2} \rfloor}\sum_{k = 1}^{\lfloor \frac{z}{2} \rfloor} \frac{(i + j + k)!}{i!(j + k)!} \frac{(j + k)!}{j!k!} g_{i, x} \cdot g_{j, y} \cdot g_{k, z} \\ & = \sum_{i = 1}^{\lfloor \frac{x}{2} \rfloor}\sum_{j = 1}^{\lfloor \frac{y}{2} \rfloor}\sum_{k = 1}^{\lfloor \frac{z}{2} \rfloor} \frac{(i + j + k)!}{i!j!k!} g_{i, x} \cdot g_{j, y} \cdot g_{k, z} \\ & = \sum_{sum=0}^{sum \le \lfloor \frac{x}{2}\rfloor + \lfloor \frac{y}{2}\rfloor} \sum_{i=0}^{sum} \frac{g_{i, x}}{i!} \times \frac{g_{sum - i, y}}{(sum-i)!} \sum_{k=0}^{\lfloor \frac{z}{2}\rfloor} (sum+k)! \times \frac{g_{k, z}}{k!} \end{align*} \]

你注意到这个可以预处理的方式去做到 \(n^2\) , 那么最终时间复杂度 \(\mathcal{O} (n^2 \omega) - \mathcal{O} (n^2)\)

具体的, 预处理 \(\displaystyle h_{sum} = \sum_{k=0}^{\lfloor \frac{z}{2}\rfloor} (sum+k)! \times \frac{g_{k, z}}{k!}\)

\[\begin{align*} f(x, y, z) = \sum_{sum=0}^{sum \le \lfloor \frac{x}{2}\rfloor + \lfloor \frac{y}{2}\rfloor} \sum_{i=0}^{sum} \frac{g_{i, x}}{i!} \times \frac{g_{sum - i, y}}{(sum-i)!} h_{sum} \end{align*} \]


感觉暴力 \(\rm{dp}\) 还有优化前途, 在这里看一下()

然而枚举状态 \(n^3\) , 寄!

实现

框架

这个题代码偏长, 抄 \(\rm{TJ}\) 太神经了, 还是自己写

首先处理 \(g\) , 然后根据

\[= \sum_{sum=0}^{sum \le \lfloor \frac{x}{2}\rfloor + \lfloor \frac{y}{2}\rfloor} \sum_{i=0}^{sum} \frac{g_{i, x}}{i!} \times \frac{g_{sum - i, y}}{(sum-i)!} \sum_{k=0}^{\lfloor \frac{z}{2}\rfloor} (sum+k)! \times \frac{g_{k, z}}{k!} \]

来处理 \(f_{a, b}\) , 带进去算即可

常见的错误:
枚举 \(sum, i\) , 需要确定 \(j\) 在范围内

代码

#include <bits/stdc++.h>
#define int long long
const int MOD = 1e9 + 7;
const int MAXW = 520; // 641
const int MAXVAL = 1020;
const int MAXNUM = 1e6 + 20, NUM = 1e6;

/*1000 以内的质数*/
int prime[] = {
    
}, cnt = 168;
int fac[MAXNUM], ifac[MAXNUM];

int add(int a, int b) { return a + b > MOD ? a + b - MOD : a + b; }
int sub(int a, int b) { return a - b < 0 ? a - b + MOD : a - b; }
int mul(int a, int b) { return (a * b * 1ll) % MOD; }
void addon(int &a, int b) { a = add(a, b); }
void mulon(int &a, int b) { a = mul(a, b); }

int quickpow(int a, int pows) {
    int base = 1;
    while (pows) {
        if (pows & 1) mulon(base, a);
        mulon(a, a), pows >>= 1;
    }
    return base;
}
void init()
{ fac[0] = fac[1] = 1; for (int i = 2; i <= NUM; i++) fac[i] = mul(i, fac[i - 1]); 
  ifac[NUM] = quickpow(fac[NUM], MOD - 2); for (int i = NUM - 1; ~i; i--) ifac[i] = mul(ifac[i + 1], i + 1); }

int n, m, w;
int r1, c1, h1, r2, c2, h2;
struct rec { int x, y, z; };

int g[MAXW][MAXVAL];
/*预处理 g*/
void gcalc() {
    g[0][0] = 1;
    for (int i = 1; i <= 500; i++) for (int j = 1; j <= 1000; j++)
        for (int p = 1; p <= cnt; p++) {
            if (prime[p] > j) break;
            addon(g[i][j], g[i - 1][j - prime[p]]);
        }
}

int h[MAXVAL];
int f(rec a, rec b) {
    int x = b.x - a.x, y = b.y - a.y, z = b.z - a.z; if (x < 0 || y < 0 || z < 0) return 0;
    memset(h, 0, sizeof h);
    for (int sum = 0; sum <= x / 2 + y / 2; sum++) for (int k = 0; k <= z / 2; k++)
        addon(h[sum], mul(mul(fac[sum + k], g[k][z]), ifac[k]));

    int res = 0;
    for (int sum = 0; sum <= x / 2 + y / 2; sum++) for (int i = 0; i <= std::min(x / 2, sum); i++) { if (sum - i > y / 2) continue;
        addon(res, mul(mul(mul(g[i][x], ifac[i]), mul(g[sum - i][y], ifac[sum - i])), h[sum])); }

    return res;
}

signed main()
{
    scanf("%lld %lld %lld", &n, &m, &w);
    scanf("%lld %lld %lld %lld %lld %lld", &r1, &c1, &h1, &r2, &c2, &h2);
    rec s = {1, 1, 1}, t = {n, m, w}, u = {r1, c1, h1}, v = {r2, c2, h2};
    init();

    gcalc();
    /*f(s, t) - [(f(s, u) * f(u, t) + f(s, v) * f(v, t)) - (f(s, u) * f(u, v) * f(v, t) + f(s, v) * f(v, u) * f(u, t))] --> ans1 - (ans2 + ans3 - ans4) */
    int ans = sub(f(s, t), sub(add(mul(f(s, u), f(u, t)), mul(f(s, v), f(v, t))), add(mul(mul(f(s, u), f(u, v)), f(v, t)), mul(mul(f(s, v), f(v, u)), f(u, t)))));

    printf("%lld", ans);
    return 0;
}

总结

容斥原理计算非法方案数
\(\rm{md}\), 见了这么多次还是想不到, 纯弱智

同类型问题, 往往可以用预处理进行转化

瞎几把拆柿子有的时候会有用, 不会做了试试
这个题其实是把 \(\rm{dp}\) 转化成组合数学, 通过移动式子把 \(j + k\) 去了, 把 \(\sum\) 丢出来一部分, 一般提出 \(i + j\) 可以解决这样的问题

posted @ 2025-01-02 16:44  Yorg  阅读(23)  评论(0)    收藏  举报