【DP or 生成函数】[CodeForces - 712D]Memory and Scores

题目大意

Memory 和 Lexa分别取数,取t轮,每一轮取的数字的范围为[k,k],并且将取的数字加他们的得分,问有多少种方案Memory的得分严格大于Lexa。

分析

算法1 :DP

很容易想到计算一个人在游戏开始后得分的动态规划。
f[i][j]表示取了i轮得j分的情况,显然

f[i][j]=l=kkf[i1][jl]

我们只需要对f[i1]求前缀和即可在O(1)的时间内转移。
最后,我们枚举Memory在游戏开始后的得分,显然,只要Lexa的得分<Memory的得分+ab即可
ans=i=k×tk×tf[t][i]j=k×ti+ab1f[t][j]

我们只需要对f[t]求前缀和即可。
由于下标可能是负数,我们平移一下坐标。

代码

ps:代码和题解略有不同,代码使用的刷表法

#include<cstdio>
#include<algorithm>
#include<cstring>
#define MAXT 100
#define MAXK 1000
#define MOD 1000000007
using namespace std;
void Read(int &x){
    static char c;
    bool f(0);
    while(c=getchar(),c!=EOF){
        if(c=='-')
            f=1;
        else if(c>='0'&&c<='9'){
            x=c-'0';
            while(c=getchar(),c>='0'&&c<='9')
                x=x*10+c-'0';
            ungetc(c,stdin);
            if(f)
                x=-x;
            return;
        }
    }
}
int f[MAXT+1][MAXT*MAXK*2+10],a,b,t,jz=100000,k,ans;
int main()
{
    Read(a),Read(b),Read(k),Read(t);
    int i,j;
    f[0][jz]=1;
    for(i=1;i<=t;i++){
        for(j=0;j<=jz*2;j++)
            if(f[i-1][j]){
                f[i][j-k]=(f[i][j-k]+f[i-1][j])%MOD;
                f[i][j+k+1]=(f[i][j+k+1]-f[i-1][j]+MOD)%MOD;
            }
        for(j=1;j<=jz*2;j++)
            f[i][j]=(f[i][j]+f[i][j-1])%MOD;
    }
    for(j=1;j<=jz*2;j++)
        f[t][j]=(f[t][j]+f[t][j-1])%MOD;
    for(j=0;j<=jz*2;j++){
        int tt=min(jz*2,j+a-b-1);
        if(tt<0)
            continue;
        else
            ans=(ans+1ll*(f[t][j]-f[t][j-1])*f[t][tt])%MOD;
    }
    printf("%d\n",ans);
}

算法2:生成函数

我们考虑上一个DP,如果我们一直计算到f[2t],那么f[2t][j]就可以表示在t轮之后两者的差值为j的方案数。

那么每一层的生成函数就是(xk+xk+1++xk),将其乘上xk,就是(1+x+x2++x2k),我们要计算(1+x+x2++x2k)2t

(1+x+x2++x2k)2t=(1x2k+11x)2t=(1x2k+1)2t×1(1x)2t=(1x2k+1)2t×(1+x+x2+x3+x4+)2t

最后xj的系数就是两者分差为j2kt的方案数
第一个因式的系数我们可以用二项式定理直接算出每一项的系数,第二个式子我们可以根据生成函数每一项所代表的意义算出。
(1+x+x2+x3+x4+)2txj项的系数就是a1+a2+a3++a2t=j的解的数量,可以用隔板法求出。
当差值+ab>0,而且差值<=2kt2kt+ba<j4kt时即可

我们可以对第二个因式的系数求一个前缀和,枚举第一个因式的每一项,然后开这一项和第二个因式的哪些项乘起来指数在[2kt+ba,4kt]区间内,就可以了。

当然,你也可以直接FFT
时间复杂度都是预处理阶乘和逆元的复杂度O(ktlogMOD)

#include<cstdio>
#include<algorithm>
#include<cstring>
#define MAXT 100
#define MAXK 1000
#define MOD 1000000007
using namespace std;
int a,b,k,t,jz,fac[MAXT*MAXK*4+1000],inv[MAXT*MAXK*4+1000],ans;
void Read(int &x){
    char c;
    while(c=getchar(),c!=EOF){
        if(c>='0'&&c<='9'){
            x=c-'0';
            while(c=getchar(),c>='0'&&c<='9')
                x=x*10+c-'0';
            ungetc(c,stdin);
            return;
        }
    }
}
int quick_pow(int a,int b){
    int ret(1);
    while(b){
        if(b&1)
            ret=1ll*ret*a%MOD;
        a=1ll*a*a%MOD;
        b>>=1;
    }
    return ret;
}
void prepare(){
    fac[0]=inv[0]=1;
    int i;
    for(i=1;i<=k*t*4+2*t;i++){
        fac[i]=1ll*fac[i-1]*i%MOD;
        inv[i]=quick_pow(fac[i],MOD-2);
    }
}
inline int C(int n,int m){
    return 1ll*fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int fz[MAXT*2+10],fm[MAXT*MAXK*4+10];
int main()
{
    Read(a),Read(b),Read(k),Read(t);
    jz=k*t;
    prepare();
    int l=2*k*t+b-a+1,r=4*k*t,i;
    if(l>r){
        puts("0");
        return 0;
    }
    for(i=0;i<=2*t;i++)
        fz[i]=(((i&1)?-1:1)*C(2*t,i)+MOD)%MOD;
    for(i=0;i<=4*k*t;i++)
        fm[i]=C(2*t-1+i+1,2*t);                          //这个经过了组合数化简,所以求的其实是前缀和
    for(i=0;i<=2*t;i++){
        int nl=l-(2*k+1)*i,nr=r-(2*k+1)*i;
        if(nr<0)
            break;
        if(nl<0)
            ans=(ans+1ll*fz[i]*fm[nr])%MOD;
        else
            ans=(ans+1ll*fz[i]*(fm[nr]-fm[nl-1]+MOD))%MOD;
    }
    printf("%d\n",ans);
}
posted @ 2016-09-12 11:07  outer_form  阅读(289)  评论(0编辑  收藏  举报