牛客多校第八场 Just Jump(容斥+dp)
题意
有一片宽度为L的海,海里有L-1块石头,这些石头的位置分别是1,2,…,L-1,你的初始位置为0。你每个时刻都要往前前进至少d距离,并且你知道,在一些时刻,一些石头的位置会被攻击,要求计算安全到达L的方案数是多少。
数据范围:$1\le d \le L \le 1e7 ,,,m \le 3000$,m为攻击的次数,每次攻击被描述为两个数字:ti,pi,代表在第ti次跳跃之后,pi位置的石头会被攻击
题解
先不考虑攻击,则总方案数可以通过dp求出:dp[i]=∑dp[j],j+d≤idp[i]=\sum dp[j],j+d\le idp[i]=∑dp[j],j+d≤i,用前缀和优化一下就能线性求出这个了。
然后考虑减去被攻击的方案,这个就要容斥了。首先我们可以将攻击按时间或位置排序,这样可以保证在受到某一次攻击之后,不会再受到这个攻击之前的攻击,这样会比较方便统计一点。然后用g[i][2]g[i][2]g[i][2]记录处在第i次攻击状态,并且前面经历了奇数次攻击和偶数次攻击的状态数,从一个攻击状态到另一个攻击状态可以用组合数计算,而这个攻击状态到L点的方案数就是dp[L−p[i]]dp[L-p[i]]dp[L−p[i]],最后减去奇数的方案数,加上偶数的方案数即可
代码
#include <bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = (int)j;i <= (int)k;i ++)
#define debug(x) cerr<<#x<<":"<<x<<endl
#define pb push_back
void test(){cerr<<"\n";}
template<typename T,typename... Args>void test(T x,Args... args){cerr<<x<<" ";test(args...);}
typedef long long ll;
const ll mod = 998244353;
ll L,d,m;
struct node{
ll t,p;
bool operator<(const node & x)const{
return t<x.t;
}
}a[3009];
ll dp[10000009],sum[10000009];
ll g[3009][2];
ll jie[10000009],ni[10000009];
ll quick(ll a,ll x){
if(x==1)return a;
if(x==0)return 1;
ll m=quick(a,x/2);
m=m*m%mod;
if(x&1)m=m*a%mod;
return m;
}
ll Ni(ll x){
if(ni[x])return ni[x];
return ni[x]=quick(jie[x],mod-2);
}
ll C(ll m,ll n){
return jie[m]*Ni(m-n)%mod*Ni(n)%mod;
}
int main() {
scanf("%lld%lld%lld",&L,&d,&m);
jie[0]=1;
for(int i=1;i<=L;i++)jie[i]=jie[i-1]*i%mod;
for(int i=1;i<=m;i++)scanf("%lld%lld",&a[i].t,&a[i].p);
dp[0]=1;
sum[0]=1;
for(int i=1;i<=L;i++){
if(i>=d)dp[i]=sum[i-d];
sum[i]=(sum[i-1]+dp[i])%mod;
}
a[0].t=0,a[0].p=0;
sort(a+1,a+1+m);
g[0][0]=1;
for(int i=1;i<=m;i++){
for(int j=0;j<=1;j++){
g[i][j]=0;
for(int k=0;k<i;k++){
if(a[k].p+(a[i].t-a[k].t)*d<=a[i].p)
g[i][j]=(g[i][j]+g[k][j^1]*C(a[i].p-a[k].p-(a[i].t-a[k].t)*(d-1)-1,(a[i].t-a[k].t)-1))%mod;
}
}
}
ll ans=0;
for(int i=0;i<=m;i++){
ans=(ans+g[i][0]*dp[L-a[i].p]%mod)%mod;
ans=(ans-g[i][1]*dp[L-a[i].p]%mod+mod)%mod;
}
printf("%lld\n",ans);
}