2019牛客暑期多校训练营第八场 J-Just Jump (dp+组合数)

本文介绍了一种使用动态规划解决带攻击路径问题的方法,包括如何计算在限制条件下到达终点的路径数量,并考虑中途遭受攻击的情况。文章详细解释了算法思路,并提供了带有注释的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

看了这篇blog学会的,大佬真的tql!本人大概重述一下大佬思路并且附上有注释的代码

题目

起点为0需要走到终点L,每次至少走d步,有m次攻击,
ti时刻会出现在pi位置,也就是说在ti时刻不能在pi位置

思路

先考虑没有攻击的方案数,这个很简单.
dp[0]=1;
dp[i]=dp[i-d]+dp[i-d-1]+…dp[0];
用一个前缀和就可以求出来

    pre[0]=1;
    for(int i=1;i<d;i++){
        pre[i]=pre[i-1];
    }
    for(ll i=d;i<=l;i++){ //dp[i]=dp[i-d]+dp[i-d-1]+..dp[0];
        dp[i]=pre[i-d];
        pre[i]=(pre[i-1]+dp[i])%mod;
    }

那么总的方案数就是dp[L]
我们只要再求出被攻击的方案数用总的减掉就ok

开始考虑攻击

考虑某一个攻击(t,p)的贡献
也就是计算从0点走t步走到p点的方案数,再乘上从p到终点的方案数就是这个攻击的贡献。

  1. 从0点走t步到p点,如果不考虑限制至少走d步,可以任意走,那么答案很显然就是C(p+t-1,t-1) 就是经典的放球问题,在t个盒子里放p个球,允许有空盒子。那如果加上限制至少走d步,就是先在每个格子里面放d个球,那么还剩p-dt个球,然后再随意走,答案就是C(p-dt+t-1,t-1)
  2. 从p走到终点L的方案数与从0走到L-p的方案数是一样的,直接就是dp[L-p]
  3. 这样对于每一个攻击,我们就计算出了它的贡献,很明显这里有重复计算了,那么我们就要减去。
考虑攻击之间的影响

在计算每一个攻击的时候,要减去他之前的攻击作为第一次攻击的方案数乘以从那个点走到它的方案数 #¥%……&()
就是我们首先计算的是它作为第一次攻击的方案数,那么我们就要减去他不是作为第一次攻击的方案数,只要减去在它之前的攻击所造成的影响就行。
也就是
记res[i]为第i次攻击为第一次攻击的贡献
res[i]=C(pi-d
ti+ti-1,ti-1)-(res[j]C(pi-pj-d(ti-tj)+ti-tj-1),ti-tj-1). (0<j<i)
简直完美!
最后答案就是dp[l]-res[i]*dp[l-pi]

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 10000005
#define sc(x) scanf("%lld",&x)
#define inf 0x3f3f3f3f
#define mod 998244353
#define pi acos(-1.0)
ll fac[maxn],inv[maxn];
ll fastpow(ll x,ll y){
    ll ans,res;
    ans=1;res=x;
    while(y){
        if(y&1)ans=ans*res%mod;
        res=res*res%mod;
        y>>=1;
    }
    return ans;
}
void init(){
    fac[0]=1;
    for(int i=1;i<maxn;i++)fac[i]=i*fac[i-1]%mod;
    inv[0]=1;
    inv[maxn-1]=fastpow(fac[maxn-1],mod-2);
    for(int i=maxn-1;i>=1;i--)
        inv[i-1]=inv[i]*i%mod;
}
ll C(ll n,ll m){
    return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
struct node{
    ll t,p;
}a[maxn];
ll dp[maxn],pre[maxn],res[3005];
bool cmp(node a,node b){
    return a.p<b.p;
}
int main(){
    init();
    ll l,d,m;
    sc(l);sc(d);sc(m); //要走到l,每步至少要走d的距离,有m个攻击点
    for(int i=1;i<=m;i++){
        sc(a[i].t);sc(a[i].p);  //在t时间会攻击p点
    }
    sort(a+1,a+1+m,cmp);  //排个序
    pre[0]=1;
    for(int i=1;i<d;i++){
        pre[i]=pre[i-1];
    }
    for(ll i=d;i<=l;i++){ //dp[i]=dp[i-d]+dp[i-d-1]+..dp[0];
        dp[i]=pre[i-d];
        pre[i]=(pre[i-1]+dp[i])%mod;
    }
    ll ans=dp[l];
    for(int i=0;i<=m;i++){
        if(a[i].p-a[i].t*d+a[i].t-1<0||a[i].t-1<0)continue;
        res[i]=C(a[i].p-a[i].t*d+a[i].t-1,a[i].t-1);//从0走到pi点,跳ti次,遭受pi点攻击的方案数
        for(int j=0;j<i;j++){
            if(a[i].p-a[j].p-d*(a[i].t-a[j].t)+a[i].t-a[j].t-1<0||a[i].t-a[j].t-1<0)continue;
            //减掉第j次攻击作为第一次攻击时的方案数乘上从tj跳到ti的方案数
            res[i]=(res[i]-res[j]*C(a[i].p-a[j].p-d*(a[i].t-a[j].t)+a[i].t-a[j].t-1,a[i].t-a[j].t-1)%mod+mod)%mod;
        }
        //总方案数减去不满足的方案数
        ans=(ans-res[i]*dp[l-a[i].p]%mod+mod)%mod;
    }
    printf("%lld\n",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值