在開始以前我要感謝y總
,是他精彩的講解才讓我對區間DP有較深的認識。c++
通常是線性結構上的對區間進行求解最值,計數的動態規劃。大體思路是枚舉斷點,而後對斷點兩邊求取最優解,而後進行合併從而得解。spa
結合模板題(合併石子)講述:https://www.acwing.com/problem/content/284/code
由於題目具備合併相鄰物品的性質,因此在合併的過程當中,必然會在最後一步出現兩個物品合二爲一的狀況,而這兩個物品則是分別由左側的物品、右側的物品合併而來的。 所以,咱們的思路是枚舉最後一步合併兩個物品時候的斷點(記爲 \(k\) ),爲了方便起見,咱們能夠將斷點放在某個物品上面。ci
結合樣例具體來講:get
k 1 3 5 2
k 1 3 5 2
k 1 3 5 2
k 1 3 5 2
上面即是四個斷點。it
對於本題,咱們記f[l][r]
爲合併 \([l,r]\) 的物品所能獲得的最小貢獻。
而斷點將 \([l,r]\) 分爲了 \([l,k],[k+1,r]\) ,這兩個區間的貢獻分別是 f[l][k]
,f[k+1][r]
而合併這兩個區間的貢獻則是 sum(l,r))
(其中sum(l,r)
表示 \([l,r]\) 的物品的權值和)模板
從而獲得遞推方程式: f[l][r] = min(f[l][r],f[l][k]+f[k+1][r]+sum(l,r))
class
能夠看出,在枚舉斷點的過程當中,咱們已經覆蓋了全部狀況(根據斷點全部可能位置分類),所以這樣作可以保證獲得答案。原理
至此,在思惟上不會有太大困難。二叉樹
下面講一下怎麼用遞推的方法求解:
由本題的邏輯結構可知,咱們要先處理出小區間的 \(f值\) 纔可以保證大區間能夠獲得更新,因此咱們第一重循環枚舉的是區間的長度len
,下面的部分則是枚舉起點(即 l
), 結合長度咱們能夠獲得 r = l+len-1
,進而咱們獲得了相應的區間 \([l,r]\) ,接下來枚舉斷點 \(k\) 便可。
結合代碼理解:
#include<bits/stdc++.h> using namespace std; const int INF=0x3f3f3f3f; const int N=305; int f[N][N]; int w[N],s[N]; int n; int main(){ cin>>n; for(int i=1;i<=n;i++){ cin>>w[i]; s[i]=s[i-1]+w[i]; } for(int len=1;len<=n;len++) for(int l=1;l+len-1<=n;l++){ int r=l+len-1; if(len==1){ f[l][r]=0; }else{ f[l][r]=INF; for(int k=l;k<r;k++) f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]); } } cout<<f[1][n]<<endl; return 0; }
固然,也能夠採起記憶化搜索,這樣不須要考慮太多。
分析
這題無非是將上題排成一列的物品放在了環上,所以咱們能夠採起斷環成鏈的技巧:
顯然,合併 \(n\) 個物品須要 \(n-1\) 步,所以,必然存在兩個物品,它們並無進行合併,那麼它們之間便出現了「斷邊」,這樣的「斷邊」並不會參與到合併的過程當中,問題便由環轉化爲鏈的狀況,因此咱們只需枚舉「斷邊」,而後進行求解便可。
有一個技巧:只需將原有的物品再按順序「複製」一份,分別獲得區間:
對於樣例:
4 5 9 4
複製:
4 5 9 4 4 5 9 4
而後依次把區間(記爲 \([s,t]\) )取出求解:
s t 4 5 9 4 4 5 9 4
s t 4 5 9 4 4 5 9 4
s t 4 5 9 4 4 5 9 4
s t 4 5 9 4 4 5 9 4
(最後一個複製的元素是沒用的,能夠忽略)
這樣分別求解四個子問題就好了。
代碼:
#include<bits/stdc++.h> using namespace std; #define INF 0x3f3f3f3f const int N = 410; int f[N][N],g[N][N]; int s[N],w[N]; int n; int main(){ cin>>n; for(int i=1;i<=n;i++){ cin>>w[i]; w[i+n]=w[i]; } memset(f,0x3f,sizeof f); memset(g,0xcf,sizeof g); for(int i=1;i<=2*n;i++) s[i]=s[i-1]+w[i]; for(int len=1;len<=n;len++){ for(int l=1;l+len-1<=n*2;l++){ int r=l+len-1; if(len==1) f[l][r]=g[l][r]=0; else{ for(int k=l;k<r;k++){ f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]); g[l][r]=max(g[l][r],g[l][k]+g[k+1][r]+s[r]-s[l-1]); } } } } int maxv=-INF,minv=INF; for(int i=1;i<=n;i++){ maxv=max(maxv,g[i][i+n-1]); minv=min(minv,f[i][i+n-1]); } cout<<minv<<endl<<maxv<<endl; return 0; }
記憶化搜索版本:(比較久以前寫的emm)
#include<bits/stdc++.h> using namespace std; #define maxn 101 int n; int a[maxn<<1]; int f_max[maxn][maxn]; int f_min[maxn][maxn]; int rec[maxn]; int s[maxn]; int sum(int l,int r){ return s[r]-s[l-1]; } int dfs_max(int l,int r){ if(l==r) return f_max[l][r]=0; if(f_max[l][r]) return f_max[l][r]; int res=0; for(int k=l;k+1<=r;k++){ res=max(res,dfs_max(l,k)+dfs_max(k+1,r)+sum(l,r)); } return f_max[l][r]=res; } int dfs_min(int l,int r){ if(l==r) return f_min[l][r]=0; if(f_min[l][r]) return f_min[l][r]; int res=INT_MAX; for(int k=l;k+1<=r;k++){ res=min(res,dfs_min(l,k)+dfs_min(k+1,r)+sum(l,r)); } return f_min[l][r]=res; } int main(){ cin>>n; for(int i=1;i<=n-1;i++) cin>>a[i],a[i+n]=a[i]; cin>>a[n]; int rec_max=0; int rec_min=INT_MAX; for(int st=1;st<=n;st++){ memset(rec,0,sizeof(rec)); memset(s,0,sizeof(s)); memset(f_max,0,sizeof(f_max)); memset(f_min,0,sizeof(f_min)); for(int i=st;i<=st+n-1;i++) rec[i-st+1]=a[i]; s[1]=rec[1]; for(int i=2;i<=n;i++) s[i]=s[i-1]+rec[i]; rec_max=max(rec_max,dfs_max(1,n)); rec_min=min(rec_min,dfs_min(1,n)); } cout<<rec_min<<endl; cout<<rec_max<<endl; return 0; }
分析
和上面題目相似(事實上區間DP的題都差很少),要注意理解是如何合併珠子的。
代碼:
#include<bits/stdc++.h> using namespace std; const int N=105; int n; int w[N<<1]; int f[N<<1][N<<1]; int main(){ cin>>n; for(int i=1;i<=n;i++){ cin>>w[i]; w[n+i]=w[i]; } for(int len=3;len<=n+1;len++) for(int l=1;l+len-1<=2*n;l++){ int r=l+len-1; for(int k=l+1;k<=r-1;k++) f[l][r]=max(f[l][r],f[l][k]+f[k][r]+w[l]*w[k]*w[r]); } int res=0; for(int i=1;i<=n;i++) res=max(res,f[i][i+n]); cout<<res<<endl; return 0; }
記憶化搜索版本:
#include<bits/stdc++.h> using namespace std; const int N=210; int n; int w[N]; int f[N][N]; int dp(int l,int r){ if(f[l][r]>=0) return f[l][r]; if(r==l || r==l+1) return f[l][r]=0; int &v=f[l][r]; for(int k=l+1;k<=r-1;k++){ v=max(v,dp(l,k)+dp(k,r)+w[l]*w[k]*w[r]); } return v; } int main(){ cin>>n; for(int i=1;i<=n;i++){ cin>>w[i]; w[n+i]=w[i]; } memset(f,-1,sizeof f); int res=0; for(int i=1;i<=n;i++) res=max(res,dp(i,i+n)); cout<<res<<endl; return 0; }
分析
g[l][r]
表示 \([l,r]\) 的根節點。
將中序遍歷的序列看做是區間求解,而後枚舉根節點(將它做爲斷點),記錄答案的過程當中要注意當答案獲得更新的時候才記錄這個區間的根節點。
#include<bits/stdc++.h> using namespace std; const int N=35; int f[N][N]; //dp int g[N][N]; //path int n; int w[N]; void dfs(int l,int r){ if(l>r) return; int root=g[l][r]; cout<<root<<' '; dfs(l,root-1); dfs(root+1,r); } int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>w[i]; for(int len=1;len<=n;len++) for(int l=1;l+len-1<=n;l++){ int r=l+len-1; if(len==1){ f[l][r]=w[l]; g[l][r]=l; } else{ for(int k=l;k<=r;k++){ int left= k==l?1:f[l][k-1]; int right= k==r?1:f[k+1][r]; int score=left*right + w[k]; if(score>f[l][r]){ f[l][r]=score; g[l][r]=k; } } } } cout<<f[1][n]<<endl; dfs(1,n); return 0; }