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

前言

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

思路

首先考虑暴力 dp

显然的我们可以暴力做 dp , 大概是 O(n3ω) 的, 其中 ω 是质数个数
考虑优化

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

ans=fs,t[fs,u×fu,t+fs,v×fv,t(fs,u×fu,v×fv,t+fs,v×fv,u×fu,t)]

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


因为直接 dp 肯定不优, 考虑组合数学计算

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

ans=(x+y+zx)(y+zy)

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

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

还是考虑操作次数中做组合数, 考虑预处理出 gi,j 表示使用 i 个质数走 j 长度的方案数, 这个是 O(n2ω) 预处理即可

先考虑 g 的转移

g0,0=1gi,j=p=1cnt[pripj]×gi1,jprip

考虑

f(x,y,z)=i=1x2j=1y2k=1z2(i+j+ki)(j+kj)gi,xgj,ygk,z

这个 O(n3) , 这不是只比直接 dp 快一个 ω ? 还要优化

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

拆!

f(x,y,z)=i=1x2j=1y2k=1z2(i+j+ki)(j+kj)gi,xgj,ygk,z=i=1x2j=1y2k=1z2(i+j+k)!i!(j+k)!(j+k)!j!k!gi,xgj,ygk,z=i=1x2j=1y2k=1z2(i+j+k)!i!j!k!gi,xgj,ygk,z=sum=0sumx2+y2i=0sumgi,xi!×gsumi,y(sumi)!k=0z2(sum+k)!×gk,zk!

你注意到这个可以预处理的方式去做到 n2 , 那么最终时间复杂度 O(n2ω)O(n2)

具体的, 预处理 hsum=k=0z2(sum+k)!×gk,zk!

f(x,y,z)=sum=0sumx2+y2i=0sumgi,xi!×gsumi,y(sumi)!hsum


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

然而枚举状态 n3 , 寄!

实现

框架#

这个题代码偏长, 抄 TJ 太神经了, 还是自己写

首先处理 g , 然后根据

=sum=0sumx2+y2i=0sumgi,xi!×gsumi,y(sumi)!k=0z2(sum+k)!×gk,zk!

来处理 fa,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;
}

总结

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

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

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

posted @   Yorg  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示