P9197 [JOI Open 2016] 摩天大楼 题解

发布时间 2023-04-15 00:49:53作者: zzxLLL

算是一道比较好想的题 (?)

首先将 \(A\) 数组排序,从小到大插入 \(f\) 中,就可以脱掉绝对值符号。

设 $f_{i,j,s,t} $ 为插入前 \(i\) 小的数,在 \(f\) 数组中形成了 \(j\) 段,对整个柿子的贡献为 \(s\) ,且确定了 \(t\) 个边界(即不能在左边界的左边或右边界的右边再插入数字)的方案数。

当插入 \(A_i\) 时,若 \(A_j\) 与其相邻,那么就会有 \(A_i - A_j\) 的贡献。但是这样并不能方便的计算。

可以用 这道题 类似的技巧,将 \(A_i - A_j\) 变成 \(\sum\limits_{p = j + 1}^i A_p-A_{p - 1}\) ,于是就可以算出每个 \(A_i - A_j\)\(s\) 的贡献了。

\(\Delta A = A_i - A_{i-1}\) ,每次插入 \(A_i\) 时,\(\Delta A\) 可以与 \((2j - t)\) 个小于 \(A_i\) 的数产生贡献。令 \(c = s + \Delta A \times (2j - t)\) ,那么有如下几种情况:

  1. 附原有的某一段中:因为附在这一段的左或右端点,但是有 \(t\) 个边界不能插数,所以可能插入的位置有 \((2j - t)\) 个。插入后段数和边界数不变,所以有 \(f_{i+1,j,c,t} = f_{i,j,s,t} \times (2j - t)\)

  2. 将某两段连成一段:只能插入到两段之间,所以可能插入的位置有 \((j-1)\) 个。插入后段数减一,边界数不变,所以 \(f_{i+1,j-1,c,t} = f_{i,j,s,t} \times (j - 1)\)

  3. 单独成段且不靠边界:只能插入在段与段或者段与边界之间,所以可能插入位置有 \((j + 1 - t)\) 个。插入后段数加一,边界数不变,所以 \(f_{i+1,j+1,s,t} = f_{i,j,s,t} \times (j + 1 - t)\)

  4. 单独成靠边界的一段:显然只有 \((2 - t)\) 个位置可以插入。插入后段数和边界数都加一,所以 \(f_{i+1,j+1,c,t+1} = f_{i,j,s,t} \times (2 - t)\)

  5. 将某一段连到边界上:显然也只有 \((2 - t)\) 个位置可以插入。插入后段数不变,边界数加一,所以 \(f_{i+1,j,c,t+1} = f_{i,j,s,t} \times (2 - t)\)

初始化 \(f_{0,0,0,0} = 1\) 。当 \(c > L\) 的时候就不必要转移了。

转移部分就写完了。最终答案就是 \(\sum\limits_{i=0}^L f_{n,1,i,2}\) 。时间复杂度 \(O(n^2 L)\)

注意 \(n = 1\) 时要特判输出 1

#include<cstdio>
#include<algorithm>
#define int long long
const int mod=1e9+7;
const int M=111,N=1111;

int f[M][M][N][3];
int n,l,a[M];

signed main(){
	scanf("%lld%lld",&n,&l);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	if(n==1) return printf("1"),0;
	std::sort(a+1,a+1+n);
	
	f[0][0][0][0]=1;
	for(int i=0;i<n;i++)
		for(int j=0;j<=i;j++)
			for(int s=0;s<=l;s++)
				for(int t=0;t<=2;t++){
					int c=(2*j-t)*(a[i+1]-a[i])+s;
					if(c<=l){
						(f[i+1][j+1][c][t]+=f[i][j][s][t]*(j+1-t)%mod)%=mod;
						if(j>0){
							(f[i+1][j][c][t]+=f[i][j][s][t]*(2*j-t)%mod)%=mod;
							(f[i+1][j-1][c][t]+=f[i][j][s][t]*(j-1)%mod)%=mod;
							if(t!=2) (f[i+1][j][c][t+1]+=f[i][j][s][t]*(2-t)%mod)%=mod;
						}
						if(t!=2) (f[i+1][j+1][c][t+1]+=f[i][j][s][t]*(2-t)%mod)%=mod;
					}
				}
	
	int ans=0;
	for(int i=0;i<=l;i++) (ans+=f[n][1][i][2])%=mod;
	printf("%lld",ans);
	return 0;
}