[BZOJ4664]Count

Count

题解

简单dp。

首先发现这道题的贡献是绝对值,有点烦,考虑如何去掉绝对值。
我们可以对 h h h从大到小排序,这样加入 h i h_{i} hi​时,已经加入的点会对其产生贡献的一定都比它大,所以我们可以直接加。
考虑每个点加入时会产生怎样的贡献:

  • 如果加入的点单独成段且不靠墙,会令贡献加上 2 h i 2h_{i} 2hi​。
  • 如果加入的点在某一段的两侧或靠一堵墙将不产生贡献。
  • 如果加入的点两侧全是墙与段,它将起连接两段的作用,会令贡献减去 ( 2 / 1 ) h i (2/1)h_{i} (2/1)hi​。

于是我们很快就想到了一个dp方法,令 d p i , j , t , k dp_{i,j,t,k} dpi,j,t,k​表示到第 i i i大的书,已经成了 j j j段,总贡献为 t t t,靠 k k k堵墙的方案数。
状态转移方程式也很好想。
但这样有个问题,因为贡献这一维带减法,所以它有可能有部分大于 L L L很多,这样空间复杂度就超了。
考虑对贡献的求法进行一下转化。

我们发现加入一个点时会对贡献产生怎样的影响。
假设有 j j j段, k k k堵墙,贡献会受到怎样的影响。由于当前加入的点是一定小于前面点的,所以对于一个段的边缘,直到合并位置,高度都在递减。
而当前插入位置递减的高度与前一本书有关,如果我们以第 i − 1 i-1 i−1本书的高度为水平线,那么加入第 i i i书后水平线将会下降 h i − 1 − h i h_{i-1}-h_{i} hi−1​−hi​。
我们此时就当所有的段都整体下降,那么它的贡献为 ( 2 j − k ) ( h i − 1 − h i ) (2j-k)(h_{i-1}-h_{i}) (2j−k)(hi−1​−hi​)。之后已经连接了的段的位置就不会下降,产生贡献了,只有剩下的两端才会产生贡献。
所以我们就将初始贡献设为 0 0 0,每次加书就看成整体下降产生贡献,这样贡献就只升不降了。
状态定义与先前一样,转移方程式也很好想,主要就是段的合并。
初始高度可以就看成最高书的高度,方便维护。

时间复杂度 O ( n 2 L ) O\left(n^2L\right) O(n2L)。

源码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;
#define MAXN 105
#define lowbit(x) (x&-x)
#define reg register
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int,int> pii;
const int mo=1e9+7;
const double PI=acos(-1.0);
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
int n,L,h[105],dp[2][105][1005][3],ans; 
bool cmp(int x,int y){return x>y;}
int add(int x,int y){return x+y<mo?x+y:x+y-mo;}
signed main(){
	read(n);read(L);
	for(int i=1;i<=n;i++)read(h[i]);
	if(n==1){puts("1");return 0;}
	sort(h+1,h+n+1,cmp);h[0]=h[1];dp[0][0][0][0]=1;
	for(int i=1;i<=n;i++){
		int now=i&1,las=now^1,dif=h[i-1]-h[i];
		for(int j=1;j<=i;j++)
			for(int k=0;k<3;k++){
				int tmp=dif*(2*j-k);
				for(int t=0;t<=L;t++){
					if(t>=tmp-2*dif)dp[now][j][t][k]=add(dp[now][j][t][k],1ll*(j-k)*dp[las][j-1][t-tmp+2*dif][k]%mo);
					if(t>=tmp)dp[now][j][t][k]=add(dp[now][j][t][k],1ll*(2*j-k)*dp[las][j][t-tmp][k]%mo);
					if(t>=tmp+2*dif)dp[now][j][t][k]=add(dp[now][j][t][k],1ll*j*dp[las][j+1][t-tmp-2*dif][k]%mo);
					if(k&&t>=tmp-dif)dp[now][j][t][k]=add(dp[now][j][t][k],1ll*(3-k)*dp[las][j-1][t-tmp+dif][k-1]%mo);
					if(k&&t>=tmp+dif)dp[now][j][t][k]=add(dp[now][j][t][k],1ll*(3-k)*dp[las][j][t-tmp-dif][k-1]%mo);
				}
			}
		for(int j=0;j<i;j++)
			for(int t=0;t<=L;t++)
				for(int k=0;k<3;k++)
					dp[las][j][t][k]=0;
	}
	for(int i=0;i<=L;i++)ans=add(ans,dp[n&1][1][i][2]);
	printf("%d\n",ans);
	return 0;
}

谢谢

上一篇:Redis-数据库、键过期的实现(1),百度Java岗一面+二面内容


下一篇:【无标题】