1. 程式人生 > >BZOJ1563/洛谷P1912 詩人小G 【四邊形不等式優化dp】

BZOJ1563/洛谷P1912 詩人小G 【四邊形不等式優化dp】

set har 方案 zoj #define 證明 isp 現在 fine

題目鏈接

洛谷P1912【原題,需輸出方案】
BZOJ1563【無SPJ,只需輸出結果】

題解

四邊形不等式

什麽是四邊形不等式?

一個定義域在整數上的函數\(val(i,j)\),滿足對\(\forall a \le b \le c \le d\)
\[val(a,d) + val(b,c) \ge val(a,c) + val(b,d)\]
那麽我們稱函數\(val(i,j)\)滿足四邊形不等式

一般地,當我們需要證明一個函數\(val(i,j)\)滿足四邊形不等式時,只需證對於\(\forall j < i\)
\[val(j,i + 1) + val(j + 1,i) \ge val(j,i) + val(j + 1,i + 1)\]


因為若該條件滿足,
那麽有
對於\(j < i\)
\[val(j,i + 1) + val(j + 1,i) \ge val(j,i) + val(j + 1,i + 1)\]
對於\(j + 1 < i\)
\[val(j + 1,i + 1) + val(j + 2,i) \ge val(j + 1,i) + val(j + 2,i + 1)\]
兩式相加
\[val(j,i + 1) + val(j + 2,i) \ge val(j,i) + val(j + 2,i + 1)\]
同理,只要滿足第一個條件,對於\(\forall j + x < i + y\)
都能推出
\[val(j,i + y) + val(j + x,i) \ge val(j,i) + val(j + x,i + y)\]
所以對\(\forall a \le b \le c \le d\)
\[val(a,d) + val(b,c) \ge val(a,c) + val(b,d)\]
證畢

如何使用四邊形不等式優化\(dp\)

如果我們有這樣一個遞推式
\[f[i] = min\{f[j] + val(j,j)\}\]
如果是\(1D1D\)方程,自然可以單調隊列或者斜率優化
但是如果\(val(i,j)\)比較復雜,無法展開,我們不能有效地分離\(i,j\)變量,斜率優化就失效了
這個時候就要考慮\(val(j,i)\)

是否滿足四邊形不等式
假使\(val(j,i)\)是滿足的

那麽我們對\(f[i]\)求出了一個最優轉移位置,即為\(p[i]\)
那麽對於\(\forall j < p[i]\),都有
\[f[p[i]] + val(p[i],i) \le f[j] + val(j,i)\]
我們現在要求\(f[k]\),其中\(k > i\)
以上我們有\(j < p[i] < i < k\)
那麽由四邊形不等式:
\[val(j,k) + val(p[i],i) \ge val(j,i) + val(p[i],k)\]
交換一下
\[val(j,i) + val(p[i],k) \le val(j,k) + val(p[i],i)\]
\(f[p[i]] + val(p[i],i) \le f[j] + val(j,i)\)相加

\[f[p[i]] + val(p[i],k) \le f[j] + val(j,k)\]
得證\(f[k]\)的決策\(p[k] \ge p[i]\)

換言之,\(f[i]\)的決策滿足決策單調性
對於\(\forall i > j\)都有\(p[i] \ge p[j]\)

我們就可以從這裏入手優化這個\(O(n^2)\)的轉移
為求出\(f[i]\),我們只需求出\(p[i]\)數組
一開始令\(p[i] = 0\),即未開始轉移前所有位置的最優決策為位置\(0\)
之後從小枚舉\(i\)
枚舉到\(i\)時,\(i\)處最優決策更新完畢,那麽\(\forall j \le i\)\(p[j]\)\(f[j]\)都已經計算
為了更新\(p[i]\)數組,我們只需找到\(i\)所能更新的最左的位置\(l\),由於決策的單調性,\([l,n]\)的決策都將更新為\(i\)
此時二分判斷即可
由於需要進行區間賦值,我們可以用一個隊列來優化以上操作
具體地,儲存若幹三元組\((pos,l,r)\),表示區間\([l,r]\)的決策為\(pos\),每次更新從隊尾逐個取出檢查即可
復雜度優化為\(O(nlogn)\)

詩人小G

\(f[i]\)為前\(i\)個句子排版的最小不和諧度
\(len[i]\)為句子\(i\)的長度,\(sum[i]\)為句子長度前綴和
容易寫出轉移方程
\[f[i] = min\{f[j] + |(sum[i] - sum[j]) + (i - j - 1) - L|^{P}\}\]
我們記\(val(j,i) = |(sum[i] - sum[j]) + (i - j - 1) - L|^{P}\),顯然無法進行斜率優化
考慮\(val(j,i)\)是否滿足四邊形不等式,即\(f[i]\)的決策是否具有單調性
我們可以打表證明
要證\(val(j,i)\)滿足四邊形不等式,只需證對\(\forall j < i\)
\[val(j,i + 1) + val(j + 1,i) \ge val(j,i) + val(j + 1,i + 1)\]
即證
\[val(j + 1,i) - val(j + 1,i + 1) \ge val(j,i) - val(j,i + 1)\]
觀察\(val(j,i) = |(sum[i] + i) - (sum[j] + j) - (L + 1)|^{P}\)
我們令\(x = (sum[i] + i) - (sum[j] + j) - (L + 1)\)
我們令\(y = (sum[i] + i) - (sum[j + 1] + j + 1) - (L + 1)\)
那麽原式化為:
\[|y|^{P} - |y + len[i] + 1|^{P} \ge |x|^{P} - |x + len[i] + 1|^{P}\]
又因為\(x > y\)
我們只需證對於函數\(f(x) = |x|^{P} - |x + c|^{P}\)關於\(x\)單調遞減,其中\(c\)為大於\(0\)的常數
可以使用導數分類討論得證

於是就可以用四邊形不等式優化成\(O(nlogn)\)

不輸出方案版:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map>
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define mp(a,b) make_pair<int,int>(a,b)
#define cls(s) memset(s,0,sizeof(s))
#define cp pair<int,int>
#define LL long double
using namespace std;
const int maxn = 100005,maxm = 100005,INF = 1000000000;
inline int read(){
    int out = 0,flag = 1; char c = getchar();
    while (c < 48 || c > 57){if (c == ‘-‘) flag = -1; c = getchar();}
    while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
    return out * flag;
}
inline void write(LL x){
    if (x / 10 >= 1) write(x / 10);
    putchar((int)floor(x - floor(x / 10) * 10) + ‘0‘);
}
int n,P,L;
LL f[maxn],a[maxn],sum[maxn];
char s[maxn][32];
int head,tail;
struct node{
    int j,l,r;
}q[maxn * 3];
LL qpow(LL a,int b){
    LL re = 1;
    for (; b; b >>= 1,a = a * a)
        if (b & 1) re = re * a;
    return re;
}
LL val(int i,int j){
    return qpow(fabs(sum[i] - sum[j] + (i - j - 1) - L),P);
}
bool check(int a,int b,int i){
    return f[a] + val(i,a) <= f[b] + val(i,b);
}
int main(){
    int T = read();
    while (T--){
        n = read(); L = read(); P = read();
        sum[0] = f[0] = 0;
        REP(i,n){
            scanf("%s",s[i] + 1),a[i] = strlen(s[i] + 1);
            sum[i] = sum[i - 1] + a[i];
            f[i] = val(i,0);
        }
        q[head = tail = 0] = (node){0,1,n};
        node u; int l,r,mid;
        for (int i = 1; i <= n; i++){
            f[i] = f[q[head].j] + val(i,q[head].j);
            q[head].l++;
            if (q[head].l > q[head].r) head++;
            while (head <= tail){
                u = q[tail--];
                if (check(i,u.j,u.l)){
                    if (head > tail){
                        q[++tail] = (node){i,i + 1,n};
                        break;
                    }
                }
                else if (!check(i,u.j,u.r)){
                    q[++tail] = u; 
                    if (u.r == n) break;
                    else{
                        q[++tail] = (node){i,u.r + 1,n};
                        break;
                    }
                }
                else {
                    l = u.l; r = u.r;
                    while (l < r){
                        mid = l + r >> 1;
                        if (check(i,u.j,mid)) r = mid;
                        else l = mid + 1;
                    }
                    q[++tail] = (node){u.j,u.l,l - 1};
                    q[++tail] = (node){i,l,n};
                    break;
                }
            }
        }
        if (f[n] > qpow(10,18)) puts("Too hard to arrange");
        else write(f[n]),puts("");
        printf("--------------------");
        if (T) puts("");
    }
    return 0;
}

輸出方案版

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map>
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define mp(a,b) make_pair<int,int>(a,b)
#define cls(s) memset(s,0,sizeof(s))
#define cp pair<int,int>
#define LL long double
using namespace std;
const int maxn = 100005,maxm = 100005,INF = 1000000000;
inline int read(){
    int out = 0,flag = 1; char c = getchar();
    while (c < 48 || c > 57){if (c == ‘-‘) flag = -1; c = getchar();}
    while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
    return out * flag;
}
inline void write(LL x){
    if (x / 10 >= 1) write(x / 10);
    putchar((int)floor(x - floor(x / 10) * 10) + ‘0‘);
}
int n,P,L,p[maxn];
LL f[maxn],a[maxn],sum[maxn];
char s[maxn][32];
int head,tail;
struct node{
    int j,l,r;
}q[maxn * 3];
LL qpow(LL a,int b){
    LL re = 1;
    for (; b; b >>= 1,a = a * a)
        if (b & 1) re = re * a;
    return re;
}
LL val(int i,int j){
    return qpow(fabs(sum[i] - sum[j] + (i - j - 1) - L),P);
}
bool check(int a,int b,int i){
    return f[a] + val(i,a) <= f[b] + val(i,b);
}
void print(int u){
    if (p[u]) print(p[u]);
    for (int i = p[u] + 1; i <= u; i++){
        printf("%s",s[i] + 1);
        putchar(i < u ? ‘ ‘ : \n);
    }
}
int main(){
    int T = read();
    while (T--){
        n = read(); L = read(); P = read();
        sum[0] = f[0] = 0;
        REP(i,n){
            scanf("%s",s[i] + 1),a[i] = strlen(s[i] + 1);
            sum[i] = sum[i - 1] + a[i];
            f[i] = val(i,0);
        }
        q[head = tail = 0] = (node){0,1,n};
        node u; int l,r,mid;
        for (int i = 1; i <= n; i++){
            f[i] = f[q[head].j] + val(i,q[head].j);
            p[i] = q[head].j;
            q[head].l++;
            if (q[head].l > q[head].r) head++;
            while (head <= tail){
                u = q[tail--];
                if (check(i,u.j,u.l)){
                    if (head > tail){
                        q[++tail] = (node){i,i + 1,n};
                        break;
                    }
                }
                else if (!check(i,u.j,u.r)){
                    q[++tail] = u; 
                    if (u.r == n) break;
                    else{
                        q[++tail] = (node){i,u.r + 1,n};
                        break;
                    }
                }
                else {
                    l = u.l; r = u.r;
                    while (l < r){
                        mid = l + r >> 1;
                        if (check(i,u.j,mid)) r = mid;
                        else l = mid + 1;
                    }
                    q[++tail] = (node){u.j,u.l,l - 1};
                    q[++tail] = (node){i,l,n};
                    break;
                }
            }
        }
        if (f[n] > qpow(10,18)) puts("Too hard to arrange");
        else{
            write(f[n]),puts("");
            print(n);
        }
        printf("--------------------");
        if (T) puts("");
    }
    return 0;
}

BZOJ1563/洛谷P1912 詩人小G 【四邊形不等式優化dp】