1. 程式人生 > >區間dp+四邊形不等式優化

區間dp+四邊形不等式優化

mes 取數 列數 scan 自己 min oid 表示 \n

區間dp+四邊形優化

luogu:p2858

題意

給出一列數 \(v_i\),每天只能取兩端的數,第 j 天取數價值為\(v_i \times j\),最大價值??

轉移方程

dp[i][j] :n天賣掉i..j貨物的收益

dp[begin][end]=max(dp[begin][end-1]+value[end]*(n-len+1) ,dp[begin+1][end]+value[begin]*(n-len+1));

註意理解

代碼

遞推形式

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mm1(x) memset(x,-1,sizeof(x))
#define maxn 2010
int dp[maxn][maxn],value[maxn];
int n;
int solve(){
    for(int i=1;i<=n;i++){
        dp[i][i]=value[i]*n;
        //*key:
    }
    //枚舉長度:
    for(int len=2;len<=n;len++){
        //枚舉起點
        for(int begin=1;begin<=n-len+1;begin++){
            int end=begin+len-1;
            dp[begin][end]=max(dp[begin][end-1]+value[end]*(n-len+1)
            ,dp[begin+1][end]+value[begin]*(n-len+1));

        }

    }
    return dp[1][n];

}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",value+i);
    }
    mm1(dp);
    printf("%d\n",solve());
    return 0;
}

記憶化搜索

#include<bits/stdc++.h>
using namespace std;
//記憶化搜素
#define maxn 2010
int dp[maxn][maxn],value[maxn];
#define mm(x) memset(x,-1,sizeof(x));
int dfs(int i,int j,int num){
    if(i>j) return 0;
    if(dp[i][j]!=-1) return dp[i][j];
    else{
        dp[i][j]=max(value[i]*num+dfs(i+1,j,num+1),
        value[j]*num+dfs(i,j-1,num+1));
    }
    return dp[i][j];
}
int n;
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",value+i);
    }
    mm(dp);
    int ans=dfs(1,n,1);
    printf("%d\n",ans);
    return 0;
}

記憶化搜索很好理解也方便些,是追求解題速度的很好選擇

p1880

題意

石子合並問題:(環形,最小值+最大值)

題解

環形,可用\(2n\)長度,將元素復制一份

轉移方程

dpmin[i][j]=min(dpmin[i][j],dpmin[i][k]+dpmin[k+1][j]+sum[j]-sum[i-1]);
對於最小值可用四邊形不等式優化
dpmax[i][j]=min(dpmax[i][j],dpmax[i][k]+dpmax[k+1][j]+sum[j]-sum[i-1]);
對於最大值,某大佬題解中提到可優化之討論端點情況(但本渣沒有弄清楚)
dpmax[i][j]=max(dpmax[i][j-1]+sum[j]-sum[i-1],dpmax[i+1][j]+sum[j]-sum[i-1]);

四邊形不等式優化

四邊形不等式優化核心滿足條件:
記決策點為\(k=s[i][j]\)
如果\(s[i][j-1]<=k<=s[i+1][j]\),則枚舉k時,只需從s[i][j-1]枚舉到s[i+1][j]$(因為這兩者區間長度較短,已經被求出)
下面是重要的定理(不加證明的使用):
對於dp[i][j]=min(dp[i][k]+dp[k+1][j]+cost[i][j])

區間包含的單調性:如果小區間包含於大區間中,那麽小區間的cost值不超過大區間的cost值
四邊形不等式:兩個交錯區間的cost的和不超過小區間與大區間的cost的和

滿足上述性質的cost,能夠推出dp[i][j]滿足四邊形不等式,s[i][j]=k也滿足上述性質。
綜上,能夠優化的關鍵在於cost[i][j]滿足上述兩個性質。*

代碼

未優化代碼:

#include<bits/stdc++.h>
using namespace std;
#define maxn 205
int dp1[maxn][maxn],dp2[maxn][maxn],value[maxn];
int sum[maxn];
//value[i]=value[i+n]
//區間dp
//dp[i][j]表示i..j最優得分
//O(N^3)
int n;
int min_ans=0x3f3f3f3f,max_ans=-1;
#define mm1(x) memset(x,-1,sizeof(x));
#define mm2(x) memset(x,0x3f,sizeof(x));
void init(){
    mm1(dp1);
    mm2(dp2);
    for(int i=1;i<=2*n;i++){
        sum[i]=sum[i-1]+value[i];
    }
}
void solve(){
    for(int i=1;i<=2*n;i++){
        dp1[i][i]=dp2[i][i]=0;

    }
    for(int len=2;len<=n;len++){
        for(int begin=1;begin<=(2*n-len+1);begin++){
            int end=begin+len-1;
            for(int j=begin;j<=end-1;j++){
                dp1[begin][end]=max(dp1[begin][end],dp1[begin][j]+dp1[j+1][end]+sum[end]-sum[begin-1]);
                dp2[begin][end]=min(dp2[begin][end],dp2[begin][j]+dp2[j+1][end]+sum[end]-sum[begin-1]);
            }
        }
    }
    for(int i=1;i<=n;i++){
        //printf("db max:%d min:%d\n",dp1[i][i+n-1],dp2[i][i+n-1]);
        max_ans=max(max_ans,dp1[i][i+n-1]);
        min_ans=min(min_ans,dp2[i][i+n-1]);
    }

}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",value+i);
        value[i+n]=value[i];
    }
    init();
    solve();
    printf("%d\n%d\n",min_ans,max_ans);
    return 0;

}

四邊形不等式優化代碼:

#include<bits/stdc++.h>
using namespace std;
//區間dp上的四邊形優化
#define inf 0x3f3f3f3f
#define maxn 210
int sum[maxn],value[maxn];
int dpmax[maxn][maxn],dpmin[maxn][maxn];
int s[maxn][maxn];// min最優決策點
int n;
#define mm0(x) memset(x,0x3f,sizeof(x))
#define mm1(x) memset(x,-1,sizeof(x))
void init(){
    mm1(dpmax);
    mm0(dpmin);
    for(int i=1;i<=2*n;i++){
        sum[i]=sum[i-1]+value[i];
        //printf("db i:%d sum[i] %d\n",i,sum[i]);
    }

}
int minv=inf,maxv=0;
void solve(){
    for(int i=1;i<=2*n;i++){
        dpmax[i][i]=dpmin[i][i]=0;
        s[i][i]=i;
    }
    
    for(int len=2;len<=n;len++){
        for(int i=1;i+len-1<=2*n;i++){
            int j=i+len-1;
            dpmax[i][j]=max(dpmax[i][j-1]+sum[j]-sum[i-1],dpmax[i+1][j]+sum[j]-sum[i-1]);
            //某大佬認為最大值取得必然最後一次合並在左右兩端
            //目前自己沒有想通和證明
            int idx;
            for(int k=s[i][j-1];k<=s[i+1][j];k++){
                if((dpmin[i][k]+dpmin[k+1][j]+sum[j]-sum[i-1])<dpmin[i][j]){
                    dpmin[i][j]=dpmin[i][k]+dpmin[k+1][j]+sum[j]-sum[i-1];
                    idx=k;
                }
                s[i][j]=idx;               
            }
           // printf("db min: %d k:%d i:%d j: %d\n",dpmin[i][j],s[i][j],i,j);
        }
    }
    for(int i=1;i<=n;i++){
        //printf("db i:%d %d\n",i,dpmin[i][i+n-1]);
        minv=min(dpmin[i][i+n-1],minv);
        maxv=max(dpmax[i][i+n-1],maxv);
    }
    
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",value+i);
        value[i+n]=value[i];
    }
    init();
    solve();
    printf("%d\n%d\n",minv,maxv);
    return 0;
}

區間dp+四邊形不等式優化