【lgj】【排列 DP】奶牛安家

DD 头奶牛,在一条数轴上选择自己的家。数轴上有 NN 个整点,分别是 00N1N-1。每头奶牛都选择一个不同的整点作为的自己的家。第 ii 头奶牛有一个安全距离 R[i]R[i],表示的意义是第 ii 头奶牛与它的邻居的距离不能小于 R[i]R[i]。也就是说,如果奶牛 ii 和奶牛 jj 是邻居,那么这两头奶牛之间的距离不能小于maxR[i],R[j]\max(R[i],R[j])。输出有多少种不同的合法安家方案,答案模 109+710^9+7

输入格式

第一行,一个整数 GG,表示有 GG 组测试数据。1<=G<=51 <=G <= 5

每组测试数据格式如下:

第一行,两个整数:NNDD1<=D<=401 <= D <= 401<=N<=1000001 <= N <= 100000

第二行,DD 个整数,第 ii 个整数是 R[i]R[i]1<=R[i]<=401 <= R[i] <=40

(倾情搬运,原题面是英文的,用了 LGJ 的题面)


如果我们知道了某个排列需要多少空间,就可以用插板法算出来该排列的情况。

定义 fi,j,kf_{i,j,k} 为还没有处理 ii 时,有 jj 个排列,需要 kk 个位置,注意,这里需要的位置不包括排列最左,最右两边的空位,否则有后效性。

同样是为了避免后效性,将 RR 升序排序。不然区间合并的时候不知道代价。

可以新增一个排列,或在当前所有排列的左,右延申,或者将两个排列合并。

那么状态转移方程就是(搬运链接,感谢这位 dalao):

为什么要这样设置呢?

如果没有新增再合并这一操作,我们无法将一个新元素放到一个排列的非中间位置。那

为什么在延伸时候要区分左,右 (×2\times 2),但是合并却不要?

因为延伸的时候,左,右会使得排列的长相不同,比如 1,2,31,2,3 延申后变成 4,1,2,34,1,2,31,2,3,41,2,3,4,但是合并的时候,状态已经包含了这两种情况,所以不能算两次。

/*
	- 别摆了
	- By yfz
*/

#include<bits/stdc++.h>
using namespace std;

//#define int long long 
const int N = 45, mod = 1e9+7, M = 1e5+10;

struct D{
	int f[N][N][N*N],n,d; // 还没处理 i,有 j 个排列,需要 k 的空间

	int fac[M],inv[M];
	int mi(int x,int k) {int p; return k?p=mi(x,k>>1),1ll*p*p%mod*(k&1?x:1)%mod:1;}
	void init() {
		fac[0] = inv[0] = 1;
		for(int i=1;i<M;i++) {
			fac[i] = 1LL * fac[i-1] * i % mod;
		}
		inv[M-1] = mi(fac[M-1],mod-2);
		for(int i=M-2;i;i--) inv[i] = 1LL * inv[i+1] * (i+1) % mod; 
	}
	
	int C(int n,int m) {
	//	return 1;
		return 1LL * fac[n] * inv[n-m] % mod * inv[m] % mod;
	}
	int a[N];
	int main() {
		init();
		cin>>n>>d;
		for(int i=1;i<=d;i++) cin>>a[i];
		sort(a+1,a+d+1);
		f[1][0][0]= 1; 
		for(int i=1;i<=d;i++) {
			for(int j=0;j<=d;j++) {
				for(int k=0;k<=min(1600,n);k++) {
					(f[i+1][j][k+a[i]] += 1LL * f[i][j][k] * 2 % mod * j % mod) %= mod;
					(f[i+1][j+1][k] += f[i][j][k]) %= mod;
					if(j > 0)
					(f[i+1][j-1][k+a[i] * 2] += 1LL * f[i][j][k] * j % mod * (j-1) % mod) %= mod; 
				}
			}
		}
		int ans = 0;
		
		for(int k=1;k<=min(n,1600);k++) {
			(ans += 1ll * f[d+1][1][k-1] * C(n-k+d,d) % mod) %= mod; 
		}
		
//		cout<<f[3][1][3]<<endl;
		cout<<ans;
		return 0;
	}
}F[5];
signed main() {
	int T; cin>>T;
	while(T--) {
		F[T].main();
	}
	return 0;
}
posted @ 2024-10-10 22:10  cjrqwq  阅读(5)  评论(0)    收藏  举报  来源