[bzoj1563][NOI2009]诗人小G——1D1D动态规划决策单调性

题目大意:

小G是一个出色的诗人,经常作诗自娱自乐。但是,他一直被一件事情所困扰,那就是诗的排版问题。
一首诗包含了若干个句子,对于一些连续的短句,可以将它们用空格隔开并放在一行中,注意一行中可以放的句子数目是没有限制的。小G给每首诗定义了一个行标准长度(行的长度为一行中符号的总个数),他希望排版后每行的长度都和行标准长度相差不远。显然排版时,不应改变原有的句子顺序,并且小G不允许把一个句子分在两行或者更多的行内。在满足上面两个条件的情况下,小G对于排版中的每行定义了一个不协调度, 为这行的实际长度与行标准长度差值绝对值的P次方,而一个排版的不协调度为所有行不协调度的总和。
小G最近又作了几首诗,现在请你对这首诗进行排版,使得排版后的诗尽量协调(即不协调度尽量小),并把排版的结果告诉他。

思路:

首先可以做一个简单的DP,即dp[i]表示写完第i句之后的最小代价,于是有dp[i]=min(dp[j]+|sum[i]-sum[j]+i-j-1-L|^k)。
这样朴素的转移是n^2的,于是可以去优化复杂度,发现dp方程可以写成这个形式:dp[i]=min(dp[j]+w[j][i]),也就是1D1D的形式:如果w[j][i]满足四边形不等式的话,那么这个DP满足决策单调性。
可以打表发现 w[j][i]满足四边形不等式,于是我们要用决策单调性来优化时间。在决策单调性的条件下,决策表只有可能长成这个样子:
111111122222222222233333333333333333333333
不难发现dp[i]在不断更新的时候,i作为决策会取代一段后缀,于是我们只需要二分后缀的起点就好了。
具体的话用一个队列去维护每一个决策点的决策区间,然后在队列中二分。
注意中间结果可能会超出1e18,用long double当作long long,因为它有科学计数法。

// luogu-judger-enable-o2
#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
typedef long double ll;

using namespace std;

void File(){
    freopen("bzoj1563.in","r",stdin);
    freopen("bzoj1563.out","w",stdout);
}

const int maxn=1e5+10;
const long long inf=LLONG_MAX>>1;
int T,n,L,P,len[maxn],sum[maxn],pre[maxn];
char poet[maxn][35];
ll dp[maxn];

struct node{int l,r,p;}qu[maxn];
int head,tail,ans[maxn],tp;

ll qpow(ll x,int y){
    ll ret=1;
    while(y){
        if(y&1)ret=ret*x;
        x=x*x;
        y>>=1;
    }
    return ret;
}

ll cal(int i,int p){return dp[p]+qpow(abs(sum[i]-sum[p]+i-p-1-L),P);}

#define mid ((l+r)>>1) 
int find(int id,int np){
    int l=qu[id].l,r=qu[id].r,p=qu[id].p;
    while(l<r){
        if(cal(mid,np)<cal(mid,p))r=mid;
        else l=mid+1;
    }
    return l;
}

void init(){
    scanf("%d%d%d",&n,&L,&P);
    REP(i,1,n){
        scanf("%s",poet[i]+1);
        len[i]=strlen(poet[i]+1);
        sum[i]=sum[i-1]+len[i];
    }
}

void print(){
    int x=n,l,r;
    while(1){
        ans[++tp]=x;
        if(!x)break;
        x=pre[x];
    }
    DREP(i,tp,1){
        l=ans[i]+1,r=ans[i-1];
        REP(j,l,r)printf("%s%c",poet[j]+1,j==r ? '\n' : ' ');
    }
}

void work(){
    head=tail=1;
    qu[tail]=(node){1,n,0};
    REP(i,1,n){
        while(qu[head].r<i)++head;
        dp[i]=cal(i,qu[head].p);
        pre[i]=qu[head].p;
        int l=head,r=tail;
        while(l<r){
            if(cal(qu[mid].r,i)<cal(qu[mid].r,qu[mid].p))r=mid;
            else l=mid+1;
        }
        if(cal(qu[l].r,i)>=cal(qu[l].r,qu[l].p))continue;
        int pos=find(l,i);
        while(qu[tail].l>=pos)--tail;
        if(qu[tail].r>=pos)qu[tail].r=pos-1;
        qu[++tail]=(node){pos,n,i};
    }
    if(dp[n]>1e18)puts("Too hard to arrange");
    else{
        printf("%.0Lf\n",dp[n]);
        print();
    }
}

int main(){
    //File();
    scanf("%d",&T);
    while(T--){
        init();
        work();
        puts("--------------------");
        tp=head=tail=0;
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值