题目大意
Memory 和 Lexa分别取数,取t轮,每一轮取的数字的范围为 [−k,k] ,并且将取的数字加他们的得分,问有多少种方案Memory的得分严格大于Lexa。
分析
算法1 :DP
很容易想到计算一个人在游戏开始后得分的动态规划。
令
f[i][j]
表示取了
i
轮得
f[i][j]=∑l=−kkf[i−1][j−l]
我们只需要对 f[i−1] 求前缀和即可在 O(1) 的时间内转移。
最后,我们枚举Memory在游戏开始后的得分,显然,只要Lexa的得分 < Memory的得分 +a−b 即可
ans=∑i=−k×tk×tf[t][i]∑j=−k×ti+a−b−1f[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
轮之后两者的差值为
那么每一层的生成函数就是
(x−k+x−k+1+⋯+xk)
,将其乘上
xk
,就是
(1+x+x2+⋯+x2k)
,我们要计算
(1+x+x2+⋯+x2k)2t
(1+x+x2+⋯+x2k)2t=(1−x2k+11−x)2t=(1−x2k+1)2t×1(1−x)2t=(1−x2k+1)2t×(1+x+x2+x3+x4+⋯)2t
最后 xj 的系数就是两者分差为 j−2kt 的方案数
第一个因式的系数我们可以用二项式定理直接算出每一项的系数,第二个式子我们可以根据生成函数每一项所代表的意义算出。
(1+x+x2+x3+x4+⋯)2t 的 xj 项的系数就是 a1+a2+a3+⋯+a2t=j 的解的数量,可以用隔板法求出。
当差值 +a−b >0,而且差值 <=2kt <script type="math/tex" id="MathJax-Element-6166"><=2kt</script>即 2kt+b−a<j≤4kt 时即可
我们可以对第二个因式的系数求一个前缀和,枚举第一个因式的每一项,然后开这一项和第二个因式的哪些项乘起来指数在 [2kt+b−a,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);
}