BZOJ4664: Count【分段DP】

题目描述:

有n本高度不同的书排成一排,混乱度为相邻高度之差的绝对值之和,求混乱度<=L的排列方案数。
n<=100,h[i]<=1000

题目分析

显然很难表示当前放的书的状态以及计算每放一本书时的对混乱度的贡献。
按照常规想法我们会想到从小到大放书,这看似没有什么用。但是大佬想到了整合贡献,分步处理的方法。题解链接
把已经放的书看成一些段,这些段有些是已经确定为边界的,有些是等待连接到其它段上的。
往一个段的边上放一本书h[i]的贡献,就相当于把边上那本书提高到h[i]用的高度。假设它是第j本书,h[i]-h[j]=(h[i]-h[i-1])+(h[i-1]-h[i-2])+…+(h[j+1]-h[j])。由此我们可以在每放一本书时就将每个段还可以放的边上的书拔高到当前书的高度,这个边上的书最终一定会与某一本书形成真的贡献。

所以状态定义就是 f [ i ] [ j ] [ k ] [ l ] f[i][j][k][l] f[i][j][k][l],表示放了前 i i i本书,形成了 j j j个段(有序),还有 k k k个边界可以放(没有确定为最终的左端点或右端点),贡献至少为 l l l的方案数。
状态转移就是新增一段,新增一段作为确定的端点,添加到某一段的边上,添加到某一段的边上作为确定的端点,以及连接两个段,五种转移。

最后的 f [ n ] [ 1 ] [ 0 ] [ l ] f[n][1][0][l] f[n][1][0][l]就一定是一个合法且方案不重复的状态。

Code:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 105
#define maxl 1005
using namespace std;
const int mod = 1e9+7;
int n,L,h[maxn],f[2][maxn][3][maxl];
inline void add(int &x,int y){(x+=y)>=mod&&(x-=mod);}
int main()
{
    scanf("%d%d",&n,&L);
    if(n==1) return puts("1"),0;
    for(int i=1;i<=n;i++) scanf("%d",&h[i]);
    sort(h+1,h+1+n);
    int now=0; f[0][1][2][0]=1,f[0][1][1][0]=2;
    for(int i=2;i<=n;i++,now=!now){
        memset(f[!now],0,sizeof f[!now]);
        for(int j=1;j<i;j++)
            for(int k=0;k<3;k++)
                for(int l=0,val,ways;l<=L;l++)
                    if((val=l+(h[i]-h[i-1])*(2*(j-1)+k))<=L&&(ways=f[now][j][k][l])){
                        if(k){
                            add(f[!now][j+1][k-1][val],ways*k%mod);//新增一段作为边界
                            add(f[!now][j][k-1][val],ways*k%mod);//添加到某一段形成边界
                        }
                        add(f[!now][j+1][k][val],1ll*ways*(j-1+k)%mod);//新增一段
                        add(f[!now][j][k][val],1ll*ways*(2*(j-1)+k)%mod);//添加到某一段
                        add(f[!now][j-1][k][val],1ll*ways*(j-1)%mod);//合并两段
                    }
    }
    int ans=0;
    for(int l=0;l<=L;l++) add(ans,f[now][1][0][l]);
    printf("%d\n",ans);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值