CodeForces 712D Memory and Scores

题目链接:http://codeforces.com/contest/712/problem/D

dp

因为每轮Memory和Lexa能取的都在[-k,k],也就是说每轮两人分数的变化量在[-2k,2k];

故可以定义状态:dp[times][diff]为第times次Memory和Lexa的分数差为diff的方案数.

而dp[times][diff]可以从dp[times-1][diff-2k]到dp[times-1][diff+2k]转移而来;

又因为变化量为-2k时的方案数为1(-k,k),

变化量为-2k+1时的方案数为2(-k,k-1;-k+1,k),

变化量为-2k+2时的方案数为3(-k,k-2;-k+1,k-1;-k+2,k),

...,

变化量为-2k+m时的方案数为m+1,

...,

变化量为0时的方案数为2k+1,

...,

变化量为2k-m时的方案数为m+1,

...,

变化量为2k-1时的方案数为2,

变化量为2k时的方案数为1.

所以状态转移方程为:dp[times][diff]=dp[times-1][diff-2k]+2*dp[times-1][diff-2k+1]+3*dp[times-1][diff-2k+2]+...+(m+1)*dp[times-1][diff-2k+m]+...+2*dp[times-1][diff+2k-1]+dp[times-1][diff+2k];

这样的话,时间复杂度为O(k2t2),代码如下:

#include<iostream>
#include<cmath>
#define M 1000000007LL
#define TIME 105
#define DIFF 300000
#define BASE 150000
using namespace std;
typedef long long LL;
LL a,b,k,t,ans;
LL dp[TIME][DIFF];
int main(void){
    cin>>a>>b>>k>>t;
    dp[0][a-b+BASE]=1;
    LL upper=a-b+BASE+2*k*t;
    LL lower=a-b+BASE-2*k*t;
    for(LL times=1;times<=t;++times){
        for(LL diff=lower;diff<=upper;diff++){
            for(LL m=0;m<=2*k;m++){
                LL add=-2*k+m;
                if(diff+add>=lower){
                    if(add)dp[times][diff]+=(dp[times-1][diff+add]+dp[times-1][diff-add])*(m+1);
                    else dp[times][diff]+=dp[times-1][diff]*(m+1);
                    dp[times][diff]%=M;
                }
            }
        }
    }
    for(int i=BASE+1;i<=upper;++i)
        ans=(ans+dp[t][i])%M;
    cout<<ans<<endl;
}

View Code

很显然,这会T,所以必须做出优化。

注意到:

dp[times][diff]是在dp[times][diff-1]的基础上前半段各个项减一,后半段各个项加一得到的,所以可以维护一个前缀和数组pre[i],那么

dp[times][diff]=dp[times][diff-1]+(pre[diff+2k]-pre[diff-1])-(pre[diff-1]-pre[(diff-1)-2k-1])

可以在O(1)的时间内完成,优化后的代码时间复杂度为O(kt2),代码如下:

#include<iostream>
#include<cmath>
#define M 1000000007LL
#define TIME 105
#define DIFF 500000
#define BASE 250000
using namespace std;
typedef long long LL;
LL a,b,k,t,ans;
LL dp[TIME][DIFF];
LL pre[DIFF];
int main(void){
    cin>>a>>b>>k>>t;
    dp[0][a-b+BASE]=1;
    LL upper=a-b+BASE+2*k*t;
    LL lower=a-b+BASE-2*k*t;
    for(LL times=1;times<=t;++times){
        for(LL diff=lower;diff<=upper;diff++)
            pre[diff]=pre[diff-1]+dp[times-1][diff],pre[diff]%=M;
        for(LL m=0;m<=2*k;m++){
            LL add=-2*k+m;
            if(add)dp[times][lower]
                +=(dp[times-1][lower+add]+dp[times-1][lower-add])*(m+1);
            else dp[times][lower]+=dp[times-1][lower]*(m+1);
            dp[times][lower]%=M;
        }
        for(LL diff=lower+1;diff<=upper;diff++){
            dp[times][diff]=dp[times][diff-1]
                +(pre[min(upper,diff+2*k)]-pre[diff-1])
                -(pre[diff-1]-pre[max(lower,diff-1-2*k)-1]);
            dp[times][diff]=(dp[times][diff]+M)%M;
            //记得+M,减法模运算可能会出现负数
        }
    }
    for(int i=BASE+1;i<=upper;++i)
        ans=(ans+dp[t][i])%M;
    cout<<ans<<endl;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值