LOJ #6374「SDWC2018 Day1」网格

模拟赛考过的题

当时太菜了现在也一样只拿到了$ 30$分

回来填个坑

LOJ #6374


题意

你要从$ (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)=\binom{z}{i}·\binom{x-(y+1)*i+z-1}{z-1}$

然后根据套路容斥得

$ calc(x,y,z)=\sum\limits_{i=0}^{limit} (-1)^ig(i)$

(貌似这里可以看成二项式反演中$ \binom{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)=\sum\limits_{i=0}^R\binom{R}{i}f(i)$

 

反演得

$ f(R)=\sum\limits_{i=0}^n (-1)^{n-i}\binom{R}{i}g(i)$

这样就解决了没有$ k$的情况

时间复杂度$ O(R^2)$

 

然后再考虑有$ k$条限制的情况(每条限制可以多次违反,注意去重!)

依旧考虑容斥

设$ g(i)$表示至少违反了$ i$条限制的方案数,$ f(i)$表示恰好违反了$ i$条限制的方案数

显然我们需要计算的是$ f(0)=\sum\limits_{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('\n');}
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;
}
posted @ 2018-12-19 11:19  Kananix  阅读(311)  评论(0编辑  收藏  举报

Contact with me