P9197 [JOI Open 2016] 摩天大楼 题解 / 连续段 dp

题目传送门:P9197 [JOI Open 2016] 摩天大楼 / Skyscraper

考虑将 \((i,f_i)\) 拍到平面上,那么图像就会类似一个波浪,其中波顶会计算两次,波底会减少两次,左右端点都只计算一次。

\(a\)大到小排序

\(f_{i,j,k,0/1,0/1}\) 表示插入了前 \(i\) 个数,目前有 \(j\) 段,且总权值为 \(k\),且左右端点是否被插入。

由于 \(a\) 从大到小排序,我们相当于从大到小插入每一个数。

转移分 \(4\) 种情况。

  • 插入的 \(i\) 将两段合并了。

显然,这个 \(i\) 一定是波底,会造成 \(-2a_i\) 的贡献。

\[jf_{i-1,j+1,k+2a_i,0,0} \to f_{i,j,k,0,0} \]

\[jf_{i-1,j+1,k+2a_i,1,0} \to f_{i,j,k,1,0} \]

\[jf_{i-1,j+1,k+2a_i,0,1} \to f_{i,j,k,0,1} \]

\[jf_{i-1,j+1,k+2a_i,1,1} \to f_{i,j,k,1,1} \]

  • 插入的 \(i\) 新开了一段。

显然,这个 \(i\) 一定是波顶,造成 \(2a_i\) 的贡献,注意考虑左右端点的情况。

\[jf_{i-1,j-1,k-2a_i,0,0} \to f_{i,j,k,0,0} \]

\[(j-1)f_{i-1,j-1,k-2a_i,1,0} + f_{i-1,j-1,k-a_i,0,0} \to f_{i,j,k,1,0} \]

\[(j-1)f_{i-1,j-1,k-2a_i,0,1} + f_{i-1,j-1,k-a_i,0,0} \to f_{i,j,k,0,1} \]

\[(j-2)f_{i-1,j-1,k-2a_i,1,1} + f_{i-1,j-1,k-a_i,0,1} + f_{i-1,j-1,k-a_i,1,0} \to f_{i,j,k,1,1} \]

  • 插入的 \(i\) 在现成段的左侧

显然这个位置不会计算任何贡献,除非在左右端点。

\[jf_{i-1,j,k,0,0} \to f_{i,j,k,0,0} \]

\[jf_{i-1,j,k,0,1} \to f_{i,j,k,0,1} \]

\[(j-1)f_{i-1,j,k,1,0} + f_{i-1,j,k+a_i,0,0} \to f_{i,j,k,1,0} \]

\[(j-1)f_{i-1,j,k,1,1} + f_{i-1,j,k+a_i,0,1} \to f_{i,j,k,1,1} \]

  • 插入的 \(i\) 在现成段的右侧

跟上面类似。

\[jf_{i-1,j,k,0,0} \to f_{i,j,k,0,0} \]

\[(j-1)f_{i-1,j,k,0,1} + f_{i-1,j,k+a_i,0,0} \to f_{i,j,k,0,1} \]

\[jf_{i-1,j,k,1,0} \to f_{i,j,k,1,0} \]

\[(j-1)f_{i-1,j,k,1,1} + f_{i-1,j,k+a_i,1,0} \to f_{i,j,k,1,1} \]

最后 \(f_{0,0,0,0,0}=1\) 然后 dp 计算即可。

注意一些边界情况。

哦哦,\(k\) 的上界好像是 \(\sum a_i\),但是好像取 \(6000\) 就能过。

#include<bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
const int N=110,M=8010,mod=1e9+7;
int f[2][N][M][2][2],n,m,a[N];
inline int read(){
	char c=getchar();
	int f=1,ans=0;
	while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
	while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
	return ans*f;
}
inline void add(int &a,int b){a=(a+b)%mod;}
main(){
	n=read(),m=read();
	if (n==1) return 0&puts("1");
	for (int i=1;i<=n;i++) a[i]=read();
	sort(a+1,a+n+1,greater<int>());
	f[0][0][0][0][0]=1;
	for (int i=1;i<=n;i++){
		for (int j=0;j<=i;j++)
			for (int k=0;k<=6000;k++) f[i&1][j][k][0][0]=f[i&1][j][k][0][1]=f[i&1][j][k][1][0]=f[i&1][j][k][1][1]=0;
		for (int j=1;j<=i;j++)
			for (int k=0;k<=6000;k++){
				add(f[i&1][j][k][0][0],j*f[i&1^1][j+1][k+2*a[i]][0][0]%mod);
				add(f[i&1][j][k][1][0],j*f[i&1^1][j+1][k+2*a[i]][1][0]%mod);
				add(f[i&1][j][k][0][1],j*f[i&1^1][j+1][k+2*a[i]][0][1]%mod);
				add(f[i&1][j][k][1][1],j*f[i&1^1][j+1][k+2*a[i]][1][1]%mod);
				if (k-2*a[i]>=0) add(f[i&1][j][k][0][0],j*f[i&1^1][j-1][k-2*a[i]][0][0]%mod);
				if (k-2*a[i]>=0) add(f[i&1][j][k][0][1],(j-1)*f[i&1^1][j-1][k-2*a[i]][0][1]%mod);
				if (k-a[i]>=0) add(f[i&1][j][k][0][1],f[i&1^1][j-1][k-a[i]][0][0]); 
				if (k-2*a[i]>=0) add(f[i&1][j][k][1][0],(j-1)*f[i&1^1][j-1][k-2*a[i]][1][0]%mod);
				if (k-a[i]>=0) add(f[i&1][j][k][1][0],f[i&1^1][j-1][k-a[i]][0][0]); 
				if (k-2*a[i]>=0&&j-2>=0) add(f[i&1][j][k][1][1],(j-2)*f[i&1^1][j-1][k-2*a[i]][1][1]%mod);
				if (k-a[i]>=0) add(f[i&1][j][k][1][1],f[i&1^1][j-1][k-a[i]][1][0]+f[i&1^1][j-1][k-a[i]][0][1]);
				add(f[i&1][j][k][0][0],j*f[i&1^1][j][k][0][0]%mod);
				add(f[i&1][j][k][0][1],j*f[i&1^1][j][k][0][1]%mod);
				add(f[i&1][j][k][1][0],(j-1)*f[i&1^1][j][k][1][0]%mod);
				add(f[i&1][j][k][1][0],f[i&1^1][j][k+a[i]][0][0]);
				add(f[i&1][j][k][1][1],(j-1)*f[i&1^1][j][k][1][1]%mod);
				add(f[i&1][j][k][1][1],f[i&1^1][j][k+a[i]][0][1]);
				add(f[i&1][j][k][0][0],j*f[i&1^1][j][k][0][0]%mod);
				add(f[i&1][j][k][0][1],(j-1)*f[i&1^1][j][k][0][1]%mod);
				add(f[i&1][j][k][0][1],f[i&1^1][j][k+a[i]][0][0]);
				add(f[i&1][j][k][1][0],j*f[i&1^1][j][k][1][0]%mod);
				add(f[i&1][j][k][1][1],(j-1)*f[i&1^1][j][k][1][1]%mod);
				add(f[i&1][j][k][1][1],f[i&1^1][j][k+a[i]][1][0]);
			} 
	}
	int ans=0;
	for (int i=0;i<=m;i++) add(ans,f[n&1][1][i][1][1]);
	cout <<ans;
    return 0;
}
posted @ 2026-01-06 12:14  OTn53_qwq  阅读(6)  评论(0)    收藏  举报