《演算法競賽進階指南》0x5B四邊形不等式 NOI2009詩人小G
阿新 • • 發佈:2020-08-05
題目連結: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; }