模拟赛考过的题
当时太菜了现在也一样只拿到了$ 30$分
回来填个坑
题意
你要从$ (0,0)$走到$ (T_x,T_y)$,每次移动的坐标增量满足$ 0 leq Delta x leq M_x,0 leq Delta y leq M_y$
不允许原地不动,且存在$ k$个坐标增量$ (k_i,k_i)$不能移动
求恰好$ R$步走到终点的方案数,对$ 1e9+7$取模
数据范围有$ T_x,T_y leq 10^6,k leq 50,R leq 1000 $
保证所有$ k_i$均为$ G$的倍数且$ 10000 leq G leq 50000$
$ Solution$
先考虑没有$ k$的限制怎么做
容易发现坐标两维是很独立的
尝试把它们分开来做
用$ calc(x,y,z)$表示走$ z$步,每步走的距离$ in [0,y]$,走到$ x$的方案数
直接$ DP$复杂度过大,尝试用容斥优化这个东西
设$ g(i)$表示至少有$i$步超出限制的情况
我们找出$ i$步让他们均移动$ y+1$的距离,然后剩下的没有限制,用插板法计算
即
$ g(i)=inom{z}{i}·inom{x-(y+1)*i+z-1}{z-1}$
然后根据套路容斥得
$ calc(x,y,z)=sumlimits_{i=0}^{limit} (-1)^ig(i)$
(貌似这里可以看成二项式反演中$ inom{n}{0}=1$的特殊情况)
这样单次计算的复杂度是$ O(R)$的
容易发现这两维并不完全独立
因为两维不允许均原地不动的情况出现
即这样算出来的$ calc(T_x,M_x,R)*calc(T_y,M_y,R)$其实是走了至多$ R$步的方案数
按照套路二项式反演
设$ g(i)$表示走了至多$ i$步的方案数,$ f(i)$表示恰好走了$ i$步的方案数
由
$ g(R)=calc(T_x,M_x,R)*calc(T_y,M_y,R)=sumlimits_{i=0}^Rinom{R}{i}f(i)$
反演得
$ f(R)=sumlimits_{i=0}^n (-1)^{n-i}inom{R}{i}g(i)$
这样就解决了没有$ k$的情况
时间复杂度$ O(R^2)$
然后再考虑有$ k$条限制的情况(每条限制可以多次违反,注意去重!)
依旧考虑容斥
设$ g(i)$表示至少违反了$ i$条限制的方案数,$ f(i)$表示恰好违反了$ i$条限制的方案数
显然我们需要计算的是$ f(0)=sumlimits_{i=0}^{limit} (-1)^i g(i)$
现在问题是$ g(i)$如何计算
我们设$ F(x,y)$表示违反了$ x$条限制,这些限制的$ k_i$之和为$ y*G$的方案数
显然$ DP$的第二维不超过$ frac{10^6}{10^4}=100$,因此可以非常轻易的计算出$ DP$的结果
然后发现$ limit$也并不大,直接利用$ F$数组计算$ g$数组的值并计算即可
理论复杂度可能高达$ 1000*1000*100*100$
但由于内层可以记忆化,复杂度能去掉一个$ 1000$
再加上数据极度不满,这个算法跑的飞快
就解决了这道题
$ my code$
#include<ctime> #include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<queue> #define p 1000000007 #define rt register int #define ll long long using namespace std; inline ll read(){ ll x=0;char zf=1;char ch=getchar(); while(ch!='-'&&!isdigit(ch))ch=getchar(); if(ch=='-')zf=-1,ch=getchar(); while(isdigit(ch))x=x*10+ch-'0',ch=getchar();return x*zf; } void write(ll y){if(y<0)putchar('-'),y=-y;if(y>9)write(y/10);putchar(y%10+48);} void writeln(const ll y){write(y);putchar(' ');} int k,m,n,x,y,z,cnt,Tx,Ty,Mx,My,R,G; int a[55],g[105]; int jc[1001010],njc[1001010],inv[1001010]; int C(int x,int y){return 1ll*jc[x]*njc[y]%p*njc[x-y]%p;} int calc(int x,int y,int bs){//每步不超过y,走的距离为x,走了bs步 if(y*bs<x)return 0;int ans=0; for(rt i=0,fla=1;i<=bs&&(y+1)*i<=x;i++,fla*=-1) (ans+=1ll*fla*C(bs,i)*C(x-(y+1)*i+bs-1,bs-1)%p)%=p; return ans; } int anss[105][1005],ans2[105][1005]; int solve(int x,int R){ int ans=0; for(rt i=R,fla=1;i>=0;i--,fla*=-1){ int v; if(anss[x/G][i])v=anss[x/G][i];else v=1ll*calc(Tx-x,Mx,i)*calc(Ty-x,My,i)%p; anss[x/G][i]=v; (ans+=1ll*v*C(R,i)*fla%p)%=p; } return (ans+p)%p; } int F[105][105];//F[i][j]走i步走出i*G的方案数 int main(){ jc[0]=jc[1]=njc[0]=njc[1]=inv[0]=inv[1]=1; Tx=read(),Ty=read(),Mx=read(),My=read(); R=read();G=read();k=read(); for(rt i=2;i<=max(Tx,Ty)+R;i++){ jc[i]=1ll*jc[i-1]*i%p; inv[i]=1ll*inv[p%i]*(p-p/i)%p; njc[i]=1ll*njc[i-1]*inv[i]%p; } if(!k)return write(solve(0,R)),0; for(rt i=1;i<=k;i++)a[i]=read(); sort(a+1,a+k+1);k=unique(a+1,a+k+1)-a-1; F[0][0]=1; g[0]+=solve(0,R); for(rt i=1;i*G<=min(Tx,Ty);i++) for(rt j=0;j*G<=min(Tx,Ty);j++){ for(rt d=1;d<=k;d++)if(a[d]<=min(Tx,Ty)&&a[d]<=j*G) (F[i][j]+=F[i-1][j-a[d]/G])%=p; (g[i]+=1ll*solve(j*G,R-i)*F[i][j]%p)%=p; } ll ret=0; for(rt i=0,fla=1;i*G<=min(Tx,Ty);i++,fla*=-1)(ret+=1ll*fla*g[i]*C(R,i)%p)%=p; cout<<(ret+p)%p; return 0; }