【BZOJ4498】魔法的碰撞(动态规划)
大致题意: 你要在\(m\)个格子中摆放\(n\)个魔法师(每个魔法师有一个攻击范围),使他们不能互相攻击,求方案数。
前言
要用魔法战胜魔法!(显然我不会魔法只会膜法,所以就被这道题吊锤了)
动态规划
这是一道比较有趣的题。
题意看起来很简单,数据范围好像也很小,于是我就先后飞快地有了两个想法,然后又先后飞快地Hack掉了自己。。。
考虑假设我们已知要占据\(k\)个格子,那么此时的方案数就相当于在\(m-k\)个格子中插入\(n\)块板的方案数,应该是\(C_{m-k+n}^n\)。
所以我们就有一个想法, 即维护要占据的格子数。
然后发现一个魔法师的攻击范围对答案有贡献,需要他旁边的魔法师攻击范围比他小。
因此,我们可以从大到小枚举每一个魔法师。
而一个攻击范围小的魔法师无论放在哪个魔法师旁边其实不会对占据的格子数造成影响,因此当我们枚举到一个魔法师时便可以决定好之后要在他旁边放多少个魔法师,然后只要记录当前预留了几个格子,就能转移了。
具体地,我们设\(f_{i,j,k}\)表示放了攻击范围前\(i\)大的魔法师,预留了\(j\)个格子,占据了\(k\)个格子的方案数。
然后就有第\(i\)个魔法师旁边决定预留\(0/1/2\)个格子的三种情况转移(注意\(i\)本身要占据一个格子),即:
\[f_{i,j,k}=(j+1)\times f_{i-1,j+1,k}+j\times f_{i-1,j,k-a_i}\times 2+(j-1)\times f_{i-1,j-1,k-2a_i}
\]
而初始条件为\(f_{0,1,1}=1\),即一开始有一个格子,第一个魔法师要占据一个格子。
具体实现可以详见代码。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 40
#define M 1000000
#define X 1000000007
#define C(x,y) (1LL*Fac[x]*IFac[y]%X*IFac[(x)-(y)]%X)
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m,a[N+5],f[N+5][N+5][N*N+5],Fac[M+5],IFac[M+5];
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
I bool cmp(CI x,CI y) {return x>y;}//从大到小排序
int main()
{
RI i,j,k,lim;if(scanf("%d%d",&m,&n),n==1) return printf("%d\n",m),0;//一个后来又被我Hack掉的想法需要特判1,干脆保留了。。。
for(Fac[0]=i=1;i<=m;++i) Fac[i]=1LL*Fac[i-1]*i%X;//预处理阶乘
for(IFac[m]=QP(Fac[m],X-2),i=m-1;~i;--i) IFac[i]=1LL*IFac[i+1]*(i+1)%X;//预处理阶乘逆元
for(i=1;i<=n;++i) scanf("%d",a+i);sort(a+1,a+n+1,cmp),lim=min(N*N,m),f[0][1][1]=1;//读入,排序,初始化f
for(i=1;i<=n;++i) for(j=0;j<=n;++j) for(k=1;k<=lim;++k)//枚举i,j,k动态规划
f[i][j][k]=1LL*(j+1)*f[i-1][j+1][k]%X,//不预留
k>=a[i]&&(f[i][j][k]=(2LL*j*f[i-1][j][k-a[i]]+f[i][j][k])%X),//预留一个格子
j&&k>=2*a[i]&&(f[i][j][k]=(1LL*(j-1)*f[i-1][j-1][k-2*a[i]]+f[i][j][k])%X);//预留两个格子
RI ans=0;for(k=1;k<=lim;++k) ans=(1LL*C(m-k+n,n)*f[n][0][k]+ans)%X;//统计答案
return printf("%d\n",ans),0;
}
待到再迷茫时回头望,所有脚印会发出光芒