2019牛客多校J.Just Jump(容斥原理)
题目大意
有一条河,长度为L,每次至少需要跳d个单位长,有m次攻击 < t , p > <t,p> <t,p>意味着时刻t攻击位置p(即此时不能跳到这个位置上)问从0跳到L有多少种跳法
解题思路
假设没有攻击的情况下,跳跃长度k可以通过递推得出
{
d
p
[
k
]
=
0
k
<
d
d
p
[
k
]
=
∑
i
=
0
k
−
d
d
p
[
i
]
k
>
=
d
\begin{cases} dp[k]=0&k<d\\ dp[k]=\sum_{i=0}^{k-d}dp[i]&k>=d \end{cases}
{dp[k]=0dp[k]=∑i=0k−ddp[i]k<dk>=d
这可以通过前缀和方便地求出
接下来需要减去有攻击的情况这里我们只需要做容斥,加上至少一次进攻的方案数,减去至少两次的方案数…
而每种的方案数可以通过 C p − d t + t − 1 t − 1 C_{p-dt+t-1}^{t-1} Cp−dt+t−1t−1乘上上一次攻击的答案再做累加得到(这里p,t的意为上次进攻和这次进攻的位置距离和时间距离)
AC代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL ;
const int mod=998244353;
const int size=1e7+5;
int L,d,m;
int dp[size];
int sub[2][3005];
int fac[size],invfac[size];
int sum[size];
struct att
{
int t,p;
bool friend operator<(att x,att y)
{
return x.t<y.t;
}
}at[3005];
int quick_pow(int a,int b)
{
int ans=1;
while(b)
{
if(b&1) ans=1LL*ans*a%mod;
b>>=1;
a=1LL*a*a%mod;
}
return ans;
}
void init()
{
fac[0]=1;
for(int i=1;i<size;i++) fac[i]=1LL*fac[i-1]*i%mod;
invfac[size-1]=quick_pow(fac[size-1],mod-2);
for(int i=size-2;i>=1;i--) invfac[i]=1LL*invfac[i+1]*(i+1)%mod;
}
int combi(LL a,LL b)
{
if(a==0) return 1;
if(a<0) return 0;
return 1LL*fac[b]*invfac[b-a]%mod*invfac[a]%mod;
}
LL calc(LL k,LL l)
{
l-=(LL)d*k;
if(l<0) return 0;
if(k==0) return l==0;
return combi(k-1,l+k-1);
}
int main()
{
scanf("%d%d%d",&L,&d,&m);
dp[0]=sum[0]=1;
init();
for(int i=1;i<=L;i++)
{
if(i>=d) dp[i]=sum[i-d];else dp[i]=0;
sum[i]=(sum[i-1]+dp[i])%mod;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&at[i].t,&at[i].p);
}
at[0].t=at[0].p=0;
sort(at+1,at+1+m);
sub[0][0]=1;
LL ans=dp[L];
for(int i=1;i<=m;i++)
{
for(int k=0;k<2;k++)
{
for(int j=0;j<i;j++)
{
sub[k][i]=(sub[k][i]+1LL*sub[k^1][j]*calc(at[i].t-at[j].t,at[i].p-at[j].p))%mod;
}
}
ans=(ans+1LL*(sub[0][i]-sub[1][i]+mod)*dp[L-at[i].p]%mod)%mod;
}
printf("%lld\n",ans);
}