看了这篇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到终点的方案数就是这个攻击的贡献。
- 从0点走t步到p点,如果不考虑限制至少走d步,可以任意走,那么答案很显然就是C(p+t-1,t-1) 就是经典的放球问题,在t个盒子里放p个球,允许有空盒子。那如果加上限制至少走d步,就是先在每个格子里面放d个球,那么还剩p-dt个球,然后再随意走,答案就是C(p-dt+t-1,t-1)
- 从p走到终点L的方案数与从0走到L-p的方案数是一样的,直接就是dp[L-p]
- 这样对于每一个攻击,我们就计算出了它的贡献,很明显这里有重复计算了,那么我们就要减去。
考虑攻击之间的影响
在计算每一个攻击的时候,要减去他之前的攻击作为第一次攻击的方案数乘以从那个点走到它的方案数 #¥%……&()
就是我们首先计算的是它作为第一次攻击的方案数,那么我们就要减去他不是作为第一次攻击的方案数,只要减去在它之前的攻击所造成的影响就行。
也就是
记res[i]为第i次攻击为第一次攻击的贡献
res[i]=C(pi-dti+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;
}