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[][a-b+BASE]=;
LL upper=a-b+BASE+*k*t;
LL lower=a-b+BASE-*k*t;
for(LL times=;times<=t;++times){
for(LL diff=lower;diff<=upper;diff++){
for(LL m=;m<=*k;m++){
LL add=-*k+m;
if(diff+add>=lower){
if(add)dp[times][diff]+=(dp[times-][diff+add]+dp[times-][diff-add])*(m+);
else dp[times][diff]+=dp[times-][diff]*(m+);
dp[times][diff]%=M;
}
}
}
}
for(int i=BASE+;i<=upper;++i)
ans=(ans+dp[t][i])%M;
cout<<ans<<endl;
}
很显然,这会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[][a-b+BASE]=;
LL upper=a-b+BASE+*k*t;
LL lower=a-b+BASE-*k*t;
for(LL times=;times<=t;++times){
for(LL diff=lower;diff<=upper;diff++)
pre[diff]=pre[diff-]+dp[times-][diff],pre[diff]%=M;
for(LL m=;m<=*k;m++){
LL add=-*k+m;
if(add)dp[times][lower]
+=(dp[times-][lower+add]+dp[times-][lower-add])*(m+);
else dp[times][lower]+=dp[times-][lower]*(m+);
dp[times][lower]%=M;
}
for(LL diff=lower+;diff<=upper;diff++){
dp[times][diff]=dp[times][diff-]
+(pre[min(upper,diff+*k)]-pre[diff-])
-(pre[diff-]-pre[max(lower,diff--*k)-]);
dp[times][diff]=(dp[times][diff]+M)%M;
//记得+M,减法模运算可能会出现负数
}
}
for(int i=BASE+;i<=upper;++i)
ans=(ans+dp[t][i])%M;
cout<<ans<<endl;
}
这样的代码仍然可以优化:
1.可以用滚动数组来优化空间复杂度,从O(kt2)降低到O(kt),太懒没写╮(╯▽╰)╭;
2.可以用快速傅里叶变换FFT优化时间复杂度,从O(kt2)继续降到O(kt lg(kt)),没学还不会写╮(╯▽╰)╭
//昨天去面试微软俱乐部被嘲讽=。= 定个目标吧,这学期div2稳定4题怎么样?