[蓝桥杯 2020 国 B] 质数行者
前言
也是终于做到了 \(\rm{WBH}\) 大佬说的好题, 听过讲的情况下也是一点没听懂
思路
首先考虑暴力 \(\rm{dp}\)
显然的我们可以暴力做 \(\rm{dp}\) , 大概是 \(\mathcal{O} (n^3 \omega)\) 的, 其中 \(\omega\) 是质数个数
考虑优化
你注意到只有两个障碍点, 这个好像很重要
考虑「容斥原理」, 正难则反, 记 \(a \to b\) 的方案数为 \(f_{a, b}\) , 设起点和终点分别为 \(s, t\) , 两个特殊点分别为 \(u, v\)
然后考虑 \(f\) 怎么求, 发现相当于走一个矩阵, 从左下到右上
因为直接 \(\rm{dp}\) 肯定不优, 考虑组合数学计算
首先我们发现, 这种问题很像之前的经典问题, 在之前的问题中
即在 \(x + y + z\) 次操作中挑选 \(x\) 次作为横向移动, 然后再剩下的 \(y + z\) 次操作中挑选 \(y\) 次作为纵向移动, 剩下的就不用计算组合数了
但是这个问题之中, 要求每次走的长度为质数, 怎么处理?
还是考虑操作次数中做组合数, 考虑预处理出 \(g_{i, j}\) 表示使用 \(i\) 个质数走 \(j\) 长度的方案数, 这个是 \(\mathcal{O} (n^2 \omega)\) 预处理即可
先考虑 \(g\) 的转移
考虑
这个 \(\mathcal{O} (n^3)\) , 这不是只比直接 \(\rm{dp}\) 快一个 \(\omega\) ? 还要优化
这个多半只能对柿子进行优化, 考虑减少 \(\sum\) 的个数
拆!
你注意到这个可以预处理的方式去做到 \(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!}\)
感觉暴力 \(\rm{dp}\) 还有优化前途, 在这里看一下()
然而枚举状态 \(n^3\) , 寄!
实现
框架
这个题代码偏长, 抄 \(\rm{TJ}\) 太神经了, 还是自己写
首先处理 \(g\) , 然后根据
来处理 \(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\) 可以解决这样的问题

浙公网安备 33010602011771号