bzoj4498: 魔法的碰撞

首先,如果排列确定,那么就可以组合学解决了,不过排列数很多,显然不能枚举。

我们发现如果区间不能重叠的话,总长度减去所有区间长度就是能用的多余格子数。

然而相邻区间可以重叠较小区间一半的长度,因此这些长度就可以作为额外的多余格子!

因此,我们发现,确定排列的目的是确定有多少多余的格子能用。

考虑把魔法师按攻击范围从大到小排序,然后逐个插入

一开始有一个洞,每插一个可以减少一个洞、不变或增加一个洞(洞就表示还能再插)

令dp[i][j][k]表示现在插了i个,有j个洞,目前有k个额外的格子。

然后随便插一插搞一搞

答案即为dp[n][0][i]*C(L+i+n,n)的和

空间时间均为O(n^4+LlogP)

//我觉得我的复杂度应该有问题……TAT

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define ll long long
#define N 43
#define M 1702
#define P 1000000007

using namespace std;
int fast_pow(int x,int y){
	int ret=1;
	while (y){
		if (y&1) ret=(ll)ret*x%P;
		x=(ll)x*x%P;
		y=y>>1;
	}
	return ret;
}
int n,d[N];
int dp[N][N][M];
int L;
int fact[1000006+M],inv[1000006+M];
int c(int n,int m){return (ll)fact[n]*inv[m]%P*inv[n-m]%P;}

int main(){
	scanf("%d%d",&L,&n);--L;
	for (int i=0;i<n;L-=2*d[i++]) scanf("%d",&d[i]);
	sort(d,d+n,greater<int>());
	memset(dp,0,sizeof(dp));
	dp[0][1][0]=1;
	for (int i=0;i<n;++i)
		for (int j=1;j<=n-i&&j<=i+1;++j)
			for (int k=0;k<=(i-j+1)*40;++k)if (dp[i][j][k]){
				(dp[i+1][j-1][k+2*d[i]]+=(ll)dp[i][j][k]*j%P)%=P;
				(dp[i+1][j][k+d[i]]+=(ll)dp[i][j][k]*j*2%P)%=P;
				(dp[i+1][j+1][k]+=(ll)dp[i][j][k]*j%P)%=P;
			}
	for (int i=fact[0]=1;i<=L+(n+2)*40;++i) fact[i]=(ll)fact[i-1]*i%P;
	for (int i=0;i<=L+(n+2)*40;++i) inv[i]=fast_pow(fact[i],P-2);
	int ans=0;
	for (int i=max(-L,0);i<=(n+1)*40;++i) (ans+=(ll)dp[n][0][i]*c(L+i+n,L+i)%P)%=P;
	printf("%d\n",ans);
	return 0;
}

  

posted @ 2016-04-07 22:03  wangyurzee  阅读(801)  评论(0编辑  收藏  举报