【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+)2t xj 项的系数就是 a1+a2+a3++a2t=j 的解的数量,可以用隔板法求出。
当差值 +ab >0,而且差值 <=2kt <script type="math/tex" id="MathJax-Element-6166"><=2kt</script>即 2kt+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);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CodeForces - 616D是一个关于找到一个序列中最长的第k好子段的起始位置和结束位置的问题。给定一个长度为n的序列和一个整数k,需要找到一个子段,该子段中不超过k个不同的数字。题目要求输出这个序列最长的第k好子段的起始位置和终止位置。 解决这个问题的方法有两种。第一种方法是使用尺取算法,通过维护一个滑动窗口来记录\[l,r\]中不同数的个数。每次如果这个数小于k,就将r向右移动一位;如果已经大于k,则将l向右移动一位,直到个数不大于k。每次更新完r之后,判断r-l+1是否比已有答案更优来更新答案。这种方法的时间复杂度为O(n)。 第二种方法是使用枚举r和双指针的方法。通过维护一个最小的l,满足\[l,r\]最多只有k种数。使用一个map来判断数的种类。遍历序列,如果当前数字在map中不存在,则将种类数sum加一;如果sum大于k,则将l向右移动一位,直到sum不大于k。每次更新完r之后,判断i-l+1是否大于等于y-x+1来更新答案。这种方法的时间复杂度为O(n)。 以上是两种解决CodeForces - 616D问题的方法。具体的代码实现可以参考引用\[1\]和引用\[2\]中的代码。 #### 引用[.reference_title] - *1* [CodeForces 616 D. Longest k-Good Segment(尺取)](https://blog.csdn.net/V5ZSQ/article/details/50750827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Codeforces616 D. Longest k-Good Segment(双指针+map)](https://blog.csdn.net/weixin_44178736/article/details/114328999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值