不失通常性和快捷性地斷定決策單調(洛谷P1912 [NOI2009]詩人小G)(動態規劃,決策單調性,單調隊列)

洛谷題目傳送門php

閒話

看完洛谷larryzhong巨佬的題解,蒟蒻一臉懵逼html

若是哪年NOI(放心我這樣的蒟蒻是去不了的)又來個決策單調性優化DP,那蒟蒻是否是會看都看不出來直接爆\(0\)?!函數

仍是要想點辦法,不失通常性也能快捷地斷定決策單調。優化

對於斷定決策單調的分析

再補一句決策單調性的概念:狀態轉移方程形如\(f_i=\min/\max_{j=1}^{i-1} g_j+w_{i,j}\),且記\(f_i\)的最優決策點爲\(p_i\)(也就是\(f_i\)\(g_{p_i}+w_{i,p_i}\)處轉移最優)若知足\(p_i\le p_{i+1}\),則該方程知足決策單調性。(摘自蒟蒻的DP優化總結spa

顯然每一個決策\(j\)能夠用一個關於\(i\)的函數\(f_j(i)\)表示。code

函數的一個重要思想:數形結合!htm

光靠腦子想不到規律,只好先舉一些用語言難以描述的反例。咱們的函數不能這樣blog

看到這裏Dalao們有沒有一點想法呢?蒟蒻反正想到了一點——兩個函數必須只有一個交點!在這一點以前一個函數更優,而以後就被永遠取代了。隊列

感受知足條件的函數其實不多,分類討論一下(若有誤歡迎Dalao指教)get

直線

顯然上面的基本要求都知足。不過要是函數是直線的話均可以用斜率優化搞了(\(k_1x+b_1\ge k_2x+b_2,x\ge\frac{b_2-b_1}{k_1-k_2}\))。

不是直線

爲了不圖1的尷尬狀況,可能須要全部決策函數之間能夠經過平移相互變換(形如\(f_j(x)=f_k(x-a)+b\))。

爲了不圖2的尷尬狀況,可能須要函數的導函數在各自的定義域內單調遞增/遞減(注意是導函數不是原函數)。

接着,根據蒟蒻肝過的幾個題,好像還有一條規律——

若是導函數遞增、求最大值(檸檬),或者導函數遞減、求最小值,要用單調棧。

若是導函數遞增、求最小值(本題),或者導函數遞減、求最大值(Lightning Conductor),要用單調隊列。

複雜的函數

蒟蒻見過這一道(Yet another minimization problem

感受能夠當作對於每一種數都有一個函數\(\frac{(c_i-c_j)(c_i-c_j+1)}{2}\),單看這一個是知足決策單調性的(\(c_i\ge c_j\),定義域內的導函數是遞增的)。

那麼總函數就能夠寫成\(f_j(i)=g_j+\sum\frac{(c_i-c_j)(c_i-c_j+1)}{2}\),怎麼看也不像是不知足決策單調性的。

本題的思路

那麼就能夠迴歸本題了。

\(len_i\)爲第\(i\)句的長度,\(s_i=i+\sum\limits_{j=1}^i len_j\)(加上\(i\)是默認一句話後面有空格)

\(f_i\)爲選前\(i\)句的最小代價,咱們枚舉當前這一行填入最後面的多少個句子,注意行末沒有空格,長度要\(-1\),那麼有方程

\[f_i=\min\limits_{j=0}^{i-1}\{f_j+|s_i-s_j-1-L|^P\}\]

容易發現後面這一坨決策函數是關於直線\(x=s_j+1+L\)對稱的。把它去絕對值,變成兩段,顯然左邊一段和右邊一段的導函數都是遞增的,左邊恆\(<0\),右邊恆\(>0\)。又由於這函數是連續的,因此固然整個函數的導函數也單調遞增咯!

用隊列維護決策二分棧的過程再也不贅述,總結裏也有。時間複雜度\(O(Tn\log n)\)

看到Dalao們都記錄了一個三元組,可蒟蒻仍是以爲沒啥必要啊。。。只要保存隊列中相鄰兩個元素的臨界值\(k\)就行了吧。

一個寫法技巧:

二分決策\(x,y(x<y)\)的臨界值的時候,左端點設成\(x\)就行了,不必設成\(1\)(難怪蒟蒻以前寫Lightning Conductor跑得有點慢)

三個坑點:

無論是轉移仍是輸出,都要去掉行末的空格(怪蒟蒻看題不清)

當答案大於\(10^{18}\)的時候開longlong也炸了,因此要用實數以犧牲精度的代價換來更大的值域。然而double真的WA了。因而要開long double。

cmath的pow太慢了容易TLE,要手寫快速冪。

#include<cstdio>
#include<cmath>
#include<cstring>
#define RG register
#define R RG int
#define G c=getchar()
#define Calc(i,j) f[j]+qpow(abs(s[i]-s[j]-L))//計算函數值
using namespace std;
typedef long double LD;//開long double
const int N=1e5+9;
int n,L,P,s[N],q[N],k[N],pr[N];
LD f[N];
char str[N][33];
inline int in(){
    RG char G;
    while(c<'-')G;
    R x=c&15;G;
    while(c>'-')x*=10,x+=c&15,G;
    return x;
}
inline LD qpow(RG LD b){//本身寫快速冪
    RG LD a=1;
    for(R k=P;k;k>>=1,b*=b)
        if(k&1)a*=b;
    return a;
}
inline int bound(R x,R y){//二分臨界值
    R l=x,r=n+1,m;//左端點設爲x減少常數
    while(l<r){
        m=(l+r)>>1;
        Calc(m,x)>=Calc(m,y)?r=m:l=m+1;
    }
    return l;
}
int main(){
    R T=in(),i,h,t;
    while(T--){
        n=in();L=in()+1;P=in();//把L處理了一下
        for(i=1;i<=n;++i){
            if(scanf("%s",str[i]));
            s[i]=s[i-1]+strlen(str[i])+1;//記前綴和
        }
        for(q[i=h=t=1]=0;i<=n;++i){
            while(h<t&&k[h]<=i)++h;
            f[i]=Calc(i,q[h]);pr[i]=q[h];//記錄轉移位置方便輸出方案
            while(h<t&&k[t-1]>=bound(q[t],i))--t;
            k[t]=bound(q[t],i);q[++t]=i;
        }
        if(f[n]>1e18)puts("Too hard to arrange");
        else{
            printf("%.0Lf\n",f[n]);
            for(q[t=0]=i=n;i;q[++t]=i=pr[i]);
            for(;t;--t){
                for(i=q[t]+1;i<q[t-1];++i)
                    printf("%s ",str[i]);
                puts(str[i]);//行末不要搞空格
            }
        }
        puts("--------------------");
    }
    return 0;
}
相關文章
相關標籤/搜索