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;
}

浙公网安备 33010602011771号