https://ac.nowcoder.com/acm/contest/888/J
题意:从0跳到L,每一步必须跳至少d格,有m组攻击
(
t
i
,
p
i
)
(t_i,p_i)
(ti,pi),表示第i次不能跳到p这里。求方案数。
思路:
如果不考虑攻击,那么可以用
O
(
L
)
O(L)
O(L)的时间求出
f
(
i
)
f(i)
f(i):跳到i的方案数。
考虑攻击,要用容斥原理。减去受1次攻击的方案数,加回受2次攻击的方案数…
只考虑一次攻击(t,p),从0走到p的方案数为:用隔板法,是C(p-d*t+t-1,t-1)。
设
d
p
(
i
,
0
/
1
)
dp(i,0/1)
dp(i,0/1)表示考虑前i次攻击,第i次攻击得手,并且当前总共被攻击过偶/奇数次,到达p的方案数。对于每个i,枚举上一次攻击j。求出后dp[i]*f[L-p]就是对答案的贡献。总共用
O
(
m
2
)
O(m^2)
O(m2)求出。
为什么这里的容斥不用
O
(
2
m
)
O(2^m)
O(2m)呢?因为状态i的转移只需要用到上一个攻击在哪里,前面如何无需计算,只需知道是奇还是偶。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mod 998244353
#define P pair<int,int>
#define F first
#define S second
#define maxn (10000000+100)
ll l,d,m,f[maxn],sum[maxn],dp[3005][2],fac[maxn],inv[maxn],ans;
P a[3005];
ll pow_mod(ll a,ll n)
{
if(n==0)return 1;
ll x=pow_mod(a,n/2);
x=x*x%mod;
if(n&1)x=x*a%mod;
return x;
}
ll C(ll n,ll m)
{
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
void init()
{
inv[0]=fac[0]=1;
for(int i=1;i<maxn;i++)fac[i]=fac[i-1]*i%mod;
inv[maxn-1]=pow_mod(fac[maxn-1],mod-2);
for(int i=maxn-2;i>=0;i--)inv[i]=inv[i+1]*(i+1)%mod;
}
ll geban(ll n,ll m,ll least)
{
if(least*m>n)return 0;
return C(n-m*(least-1)-1,m-1);
}
int main()
{
//freopen("input.in","r",stdin);
cin>>l>>d>>m;
for(int i=1;i<=m;i++)scanf("%d%d",&a[i].F,&a[i].S);
sort(a+1,a+1+m);
f[0]=sum[0]=1;
for(int i=1;i<d;i++)sum[i]=1;
for(int i=d;i<=l;i++)f[i]=sum[i-d],sum[i]=(sum[i-1]+f[i])%mod;
ans=f[l];
init();
for(int i=1;i<=m;i++)
{
dp[i][1]=geban(a[i].S,a[i].F,d);
for(int j=1;j<i;j++)if(a[i].S>a[j].S && a[i].F>a[j].F)
{
dp[i][0]+=dp[j][1]*geban(a[i].S-a[j].S,a[i].F-a[j].F,d),dp[i][0]%=mod;
dp[i][1]+=dp[j][0]*geban(a[i].S-a[j].S,a[i].F-a[j].F,d),dp[i][1]%=mod;
}
ans=(ans-dp[i][1]*f[l-a[i].S]%mod+mod)%mod;
ans=(ans+dp[i][0]*f[l-a[i].S]%mod)%mod;
}
cout<<ans<<endl;
return 0;
}