1. 程式人生 > 實用技巧 >《演算法競賽進階指南》0x5B四邊形不等式 NOI2009詩人小G

《演算法競賽進階指南》0x5B四邊形不等式 NOI2009詩人小G

題目連結:https://www.acwing.com/problem/content/description/306/

給出n行字串,現在將其排版,定義一個不協排程,dp中只需要記錄階段為前i個句子已經排好版,不需要記錄排了多少行。通過dp進行轉移之後發現如果用樸素演算法一定會超時。

經過對代價函式的計算髮現其滿足四邊形不等式,所以可以用決策單調性以及佇列進行優化。

其中佇列中維護的是一個決策以及決策當前覆蓋的區間,也就是在這個當前的區間中,該決策是區間中每個位置的最優決策。

程式碼:

#include<iostream>
#include<cstdio> 
#include
<algorithm> #include<cstring> using namespace std; typedef long long ll; typedef long double ld; const int N = 1000010; int n,l,p; ld f[N]; int s[N]; struct node{ int x,l,r;//x為決策,[l,r]表示決策x在當前是f[l~r]的最優決策 }q[N]; ld calc(int i,int j){//採用決策j計算f[i],注意可能會爆double ld ans=1,num=abs((ld)(s[i]-s[j]+i-j-1
-l)); for(int i=1;i<=p;i++)ans*=num; return ans+f[j]; } int get(int i,int L,int R){//找到i所屬的一段對應的決策 int ans; while(L <= R){ int mid=L+R>>1; if(q[mid].l<=i && q[mid].r>=i){ ans=mid; break; } else if(q[mid].r<i)L=mid+1
; else R=mid-1; } return q[ans].x; } void insert(int i,int &L,int &R){ int w=-1; while(L <= R){ //i是比這一段中原來的決策更優的決策 if(calc(q[R].l,i)<=calc(q[R].l,q[R].x))w=q[R--].l; else{ //對r來說,i是更優的決策,但是對l來說,i不是更優的決策 if(calc(q[R].r,q[R].x)>calc(q[R].r,i)){ int l=q[R].l,r=q[R].r; while(l < r){ int mid=l+r>>1; //i更優 if(calc(mid,i) > calc(mid,q[R].x))l=mid+1; else r=mid; } q[R].r=l-1;//l是滿足i是最優決策的第一個位置 w=l; } break; } } if(w!=-1){ q[++R].l=w; q[R].r=n; q[R].x=i; } } void G(){ cin>>n>>l>>p; for(int i=1;i<=n;i++){ char str[40]; scanf("%s",str); s[i]=s[i-1]+strlen(str);//前i個字元的長度 } int L=1,R=1; q[1].x=0; q[1].l=1; q[1].r=n; for(int i=1;i<=n;i++){ int j=get(i,L,R);//查詢覆蓋i的區間對應的決策 f[i]=calc(i,j);//使用決策j計算f[i] while(L<=R && q[L].r<=i)L++;//刪除隊頭的無用決策 q[L].l=i+1; insert(i,L,R); //使用i決策更新對[i+1,n]的決策 } if(f[n] > 1e18)puts("Too hard to arrange") ; else cout<<(ll)f[n]<<endl; puts("--------------------"); } int main(){ int t; cin>>t; while(t--){ G(); } return 0; }

實際上可以再做一次優化,因為隊首的區間就是i+1開始的,所以下一個決策直接取隊頭的就可了,不用get函式取得到包含i+1的區間對應的決策。

程式碼:

#include<iostream>
#include<cstdio> 
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
typedef long double ld;
const int N = 1000010;
int n,l,p;
ld f[N];
int s[N];
struct node{
    int x,l,r;//x為決策,[l,r]表示決策x在當前是f[l~r]的最優決策 
}q[N];
ld calc(int i,int j){//採用決策j計算f[i],注意可能會爆double 
    ld ans=1,num=abs((ld)(s[i]-s[j]+i-j-1-l));
    for(int i=1;i<=p;i++)ans*=num;
    return ans+f[j];
}
void insert(int i,int &L,int &R){
    int w=-1;
    while(L <= R){
        //i是比這一段中原來的決策更優的決策 
        if(calc(q[R].l,i)<=calc(q[R].l,q[R].x))w=q[R--].l;
        else{
            //對r來說,i是更優的決策,但是對l來說,i不是更優的決策 
            if(calc(q[R].r,q[R].x)>calc(q[R].r,i)){
                int l=q[R].l,r=q[R].r;
                while(l < r){
                    int mid=l+r>>1;
                    //i更優 
                    if(calc(mid,i) > calc(mid,q[R].x))l=mid+1;
                    else r=mid;
                }
                q[R].r=l-1;//l是滿足i是最優決策的第一個位置 
                w=l;
            }
            break;
        } 
    }
    if(w!=-1){
        q[++R].l=w;
        q[R].r=n;
        q[R].x=i;
    }
} 
void G(){
    cin>>n>>l>>p;
    for(int i=1;i<=n;i++){
        char str[40];
        scanf("%s",str);
        s[i]=s[i-1]+strlen(str);//前i個字元的長度 
    }
    
    int L=1,R=1;
    q[1].x=0; 
    q[1].l=1;
    q[1].r=n;
    for(int i=1;i<=n;i++){
        f[i]=calc(i,q[L].x);//使用決策j計算f[i] 
        while(L<=R && q[L].r<=i)L++;//刪除隊頭的無用決策 
        q[L].l=i+1;
        insert(i,L,R); //使用i決策更新對[i+1,n]的決策 
    }
    if(f[n] > 1e18)puts("Too hard to arrange") ;
    else cout<<(ll)f[n]<<endl;
    puts("--------------------");
}
int main(){
    int t;
    cin>>t;
    while(t--){
        G();
    }
    return 0;
}