[JOI Open 2016] 摩天大楼

一、题目

点此看题

二、解法

这种绝对值求和可以当成一种模型来积累了,套路是微元贡献法(在 ( t CF) 的一道网络流题也出现过)

我们先把权值离散化,对于离散化后的 (i<j)(|v_j-v_i|=sum_{k=i}^{j-1}v_{k+1}-v_{k}),那么 (v_{k+1}-v_k) 的贡献是满足 (ileq k,k+1leq j) 的对数。根据这个转化题意,我们按从小到大插入 (a) 构成序列,那么 (v_{k+1}-v_k) 的贡献次数就是前 (k) 个数构成连续段的端点个数,因为端点旁边以后一定会插入比 (k) 大的数,就会产生贡献,有点费用提前计算的感觉了。

这提示我们可以使用连续段 (dp) 来解决这个问题,因为边界(端点是 (1/n))并不会产生贡献所以我们要把它单独记录,设 (f[i][j][k][d]) 表示插入了 (i) 个数,有 (j) 个连续段,贡献和是 (k),有 (d) 个端点的方案数,每次增量法考虑一个新数的插入,新的贡献和为 (k'=k+(v_{i+1}-v_i) imes(2j-d))

  • 作为一个新的连续段插入到不为边界的空隙中:(f[i+1][j+1][k'][d]leftarrow f[i][j][k][d] imes (j+1-d))
  • 合并两个连续段:(f[i+1][j-1][k'][d]leftarrow f[i][j][k][d] imes(j-1))
  • 添加到某个连续段非边界的端点处:(f[i+1][j][k'][d]leftarrow f[i][j][k][d] imes(2j-d))
  • 作为一个新的连续段,插到边界处:(f[i+1][j+1][k'][d+1]leftarrow f[i][j][k][d] imes (2-d))
  • 添加到某个连续段作为边界:(f[i+1][j][k'][d+1]leftarrow f[i][j][k][d] imes(2-d))

时间复杂度 (O(n^2k)),最后答案显然是 (sum f[n][1][i][2]),但是要特判 (n=1)

三、总结

对于大小关系之类的限制(如绝对值),可以离散化之后取微元再贡献回去。

连续段 (dp) 是解决序列计数问题的利器,学会主动使用它。

#include <cstdio>
#include <algorithm>
using namespace std;
const int M = 105;
const int N = 1005;
const int MOD = 1e9+7;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,ans,a[M],f[M][M][N][3];
void add(int &x,int y) {x=(x+y)%MOD;}
signed main()
{
	n=read();m=read();
	if(n==1) {puts("1");return 0;}
	for(int i=1;i<=n;i++)
		a[i]=read();
	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 k=0;k<=m;k++) for(int d=0;d<=2;d++)
		{
			int kk=k+(2*j-d)*(a[i+1]-a[i]),t=f[i][j][k][d];
			if(kk>m || !t) continue;
			//Case1:insert an seg without forming boundary
			add(f[i+1][j+1][kk][d],t*(j+1-d));
			//Case2:merge two seg
			if(j) add(f[i+1][j-1][kk][d],t*(j-1));
			//Case3:add to a seg
			if(j) add(f[i+1][j][kk][d],t*(2*j-d));
			//Case4:insert an seg,forming boundary
			if(d<2) add(f[i+1][j+1][kk][d+1],t*(2-d));
			//Case5:add to a seg,forming boundary
			if(d<2 && j) add(f[i+1][j][kk][d+1],t*(2-d));
		}
	for(int i=0;i<=m;i++) add(ans,f[n][1][i][2]);
	printf("%lld
",ans);
}
原文地址:https://www.cnblogs.com/C202044zxy/p/15216300.html