石子合併問題(直線版)
題目:
石子合併(一)
時間限制:1000 ms | 記憶體限制:65535 KB 難度:3- 描述
- 有N堆石子排成一排,每堆石子有一定的數量。現要將N堆石子併成為一堆。合併的過程只能每次將相鄰的兩堆石子堆成一堆,每次合併花費的代價為這兩堆石子的和,經過N-1次合併後成為一堆。求出總的代價最小值。
- 輸入
- 有多組測試資料,輸入到檔案結束。
每組測試資料第一行有一個整數n,表示有n堆石子。
接下來的一行有n(0< n <200)個數,分別表示這n堆石子的數目,用空格隔開 - 輸出
- 輸出總代價的最小值,佔單獨的一行
- 樣例輸入
-
3 1 2 3 7 13 7 8 16 21 4 18
- 樣例輸出
-
9 239
最普通的演算法O(n^3):
1 #include <fstream> 2 #include <iostream> 3 #include <cstdio> 4 #include <cstring> 5 #include <cstdlib> 6 #include <cmath> 7 using namespace std; 8 9 const int N=205; 10 const int INF=0x7fffffff; 11 int n; 12224msint a[N],sum[N],dp[N][N]; 13 14 void f(); 15 16 int main(){ 17 //freopen("D:\\input.in","r",stdin); 18 while(~scanf("%d",&n)){ 19 sum[0]=0; 20 for(int i=1;i<=n;i++){ 21 scanf("%d",&a[i]); 22 sum[i]=sum[i-1]+a[i]; 23 } 24 f();25 printf("%d\n",dp[1][n]); 26 } 27 return 0; 28 } 29 void f(){ 30 for(int i=1;i<=n;i++) dp[i][i]=0; 31 for(int r=1;r<n;r++){ 32 for(int i=1;i<n;i++){ 33 int j=i+r; 34 if(j>n) break; 35 dp[i][j]=INF; 36 for(int k=i;k<=j;k++){ 37 dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]); 38 } 39 dp[i][j]+=sum[j]-sum[i-1]; 40 } 41 } 42 }
其中,dp[i][j]代表i到j堆的最優值,sum[i]代表第1堆到第i堆的數目總和。有:dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j])+sum[j]-sum[i-1]。
考慮四邊形不等式優化:接近O(n^2):
1 #include <fstream> 2 #include <iostream> 3 #include <cstdio> 4 #include <cstring> 5 #include <cstdlib> 6 #include <cmath> 7 using namespace std; 8 9 const int N=205; 10 const int INF=0x7fffffff; 11 int n; 12 int a[N],sum[N],dp[N][N],s[N][N]; 13 14 void f(); 15 16 int main(){ 17 //freopen("D:\\input.in","r",stdin); 18 while(~scanf("%d",&n)){ 19 sum[0]=0; 20 for(int i=1;i<=n;i++){ 21 scanf("%d",&a[i]); 22 sum[i]=sum[i-1]+a[i]; 23 } 24 f(); 25 printf("%d\n",dp[1][n]); 26 } 27 return 0; 28 } 29 void f(){ 30 for(int i=1;i<=n;i++) dp[i][i]=0,s[i][i]=i; 31 for(int r=1;r<n;r++){ 32 for(int i=1;i<n;i++){ 33 int j=i+r; 34 if(j>n) break; 35 dp[i][j]=INF; 36 for(int k=s[i][j-1];k<=s[i+1][j];k++){ 37 if(dp[i][j]>dp[i][k]+dp[k+1][j]){ 38 dp[i][j]=dp[i][k]+dp[k+1][j]; 39 s[i][j]=k; 40 } 41 } 42 dp[i][j]+=sum[j]-sum[i-1]; 43 } 44 } 45 }32ms
優化:原狀態轉移方程中的k的列舉範圍便可以從原來的(i~j-1)變為(s[i,j-1]~s[i+1,j])。
解釋下:
四邊形不等式優化動態規劃原理:
1.當決策代價函式w[i][j]滿足w[i][j]+w[i’][j’]<=w[I;][j]+w[i][j’](i<=i’<=j<=j’)時,稱w滿足四邊形不等式.當函式w[i][j]滿足w[i’][j]<=w[i][j’] i<=i’<=j<=j’)時,稱w關於區間包含關係單調.
2.如果狀態轉移方程m為且決策代價w滿足四邊形不等式的單調函式(可以推匯出m亦為滿足四邊形不等式的單調函式),則可利用四邊形不等式推出最優決策s的單調函式性,從而減少每個狀態的狀態數,將演算法的時間複雜度由原來的O(n^3)降低為O(n^2).方法是通過記錄子區間的最優決策來減少當前的決策量.令:
s[i][j]=max{k | ma[i][j] = m[i][k-1] + m[k][j] + w[i][j]}
由於決策s具有單調性,因此狀態轉移方程可修改為:
證明過程: (轉載)
設m[i,j]表示動態規劃的狀態量。
m[i,j]有類似如下的狀態轉移方程:
m[i,j]=opt{m[i,k]+m[k,j]}(i≤k≤j)
如果對於任意的a≤b≤c≤d,有m[a,c]+m[b,d]≤m[a,d]+m[b,c],那麼m[i,j]滿足四邊形不等式。
以上是適用這種優化方法的必要條件
對於一道具體的題目,我們首先要證明它滿足這個條件,一般來說用數學歸納法證明,根據題目的不同而不同。
通常的動態規劃的複雜度是O(n3),我們可以優化到O(n2)
設s[i,j]為m[i,j]的決策量,即m[i,j]=m[i,s[i,j]]+m[s[i,j]+j]
我們可以證明,s[i,j-1]≤s[i,j]≤s[i+1,j] (證明過程見下)
那麼改變狀態轉移方程為:
m[i,j]=opt{m[i,k]+m[k,j]} (s[i,j-1]≤k≤s[i+1,j])
複雜度分析:不難看出,複雜度決定於s的值,以求m[i,i+L]為例,
(s[2,L+1]-s[1,L])+(s[3,L+2]-s[2,L+1])…+(s[n-L+1,n]-s[n-L,n-1])=s[n-L+1,n]-s[1,L]≤n
所以總複雜度是O(n2)
對s[i,j-1]≤s[i,j]≤s[i+1,j]的證明:
設mk[i,j]=m[i,k]+m[k,j],s[i,j]=d
對於任意k<d,有mk[i,j]≥md[i,j](這裡以m[i,j]=min{m[i,k]+m[k,j]}為例,max的類似),接下來只要證明mk[i+1,j]≥md[i+1,j],那麼只有當s[i+1,j]≥s[i,j]時才有可能有ms[i+1,j][i+1,j]≤md[i+1,j]
(mk[i+1,j]-md[i+1,j]) - (mk[i,j]-md[i,j])
=(mk[i+1,j]+md[i,j]) - (md[i+1,j]+mk[i,j])
=(m[i+1,k]+m[k,j]+m[i,d]+m[d,j]) - (m[i+1,d]+m[d,j]+m[i,k]+m[k,j])
=(m[i+1,k]+m[i,d]) - (m[i+1,d]+m[i,k])
∵m滿足四邊形不等式,∴對於i<i+1≤k<d有m[i+1,k]+m[i,d]≥m[i+1,d]+m[i,k]
∴(mk[i+1,j]-md[i+1,j])≥(mk[i,j]-md[i,j])≥0
∴s[i,j]≤s[i+1,j],同理可證s[i,j-1]≤s[i,j]
證畢
擴充套件:
以上所給出的狀態轉移方程只是一種比較一般的,其實,很多狀態轉移方程都滿足四邊形不等式優化的條件。
解決這類問題的大概步驟是:
0.證明w滿足四邊形不等式,這裡w是m的附屬量,形如m[i,j]=opt{m[i,k]+m[k,j]+w[i,j]},此時大多要先證明w滿足條件才能進一步證明m滿足條件
1.證明m滿足四邊形不等式
2.證明s[i,j-1]≤s[i,j]≤s[i+1,j]
GarsiaWachs演算法優化:
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 using namespace std; 5 6 const int N = 205; 7 int stone[N]; 8 int n,t,ans; 9 10 void combine(int k); 11 12 int main(){ 13 while(cin>>n){ 14 for(int i=0;i<n;i++) 15 scanf("%d",stone+i); 16 t = 1; 17 ans = 0; 18 for(int i=1;i<n;i++){ 19 stone[t++] = stone[i]; 20 while(t >= 3 && stone[t-3] <= stone[t-1]) 21 combine(t-2); 22 } 23 while(t > 1) 24 combine(t-1); 25 printf("%d\n",ans); 26 } 27 return 0; 28 } 29 void combine(int k){ 30 int tmp = stone[k] + stone[k-1]; 31 ans += tmp; 32 for(int i=k;i<t-1;i++) 33 stone[i] = stone[i+1]; 34 t--; 35 int j = 0; 36 for(j=k-1;j>0 && stone[j-1] < tmp;j--) 37 stone[j] = stone[j-1]; 38 stone[j] = tmp; 39 while(j >= 2 && stone[j] >= stone[j-2]){ 40 int d = t - j; 41 combine(j-1); 42 j = t - d; 43 } 44 }4ms
解釋:
對於石子合併問題,有一個最好的演算法,那就是GarsiaWachs演算法。時間複雜度為O(n^2)。
它的步驟如下:
設序列是stone[],從左往右,找一個滿足stone[k-1] <= stone[k+1]的k,找到後合併stone[k]和stone[k-1],再從當前位置開始向左找最大的j,使其滿足stone[j] > stone[k]+stone[k-1],插到j的後面就行。一直重複,直到只剩下一堆石子就可以了。在這個過程中,可以假設stone[-1]和stone[n]是正無窮的。
舉個例子: 186 64 35 32 103 因為35<103,所以最小的k是3,我們先把35和32刪除,得到他們的和67,並向前尋找一個第一個超過67的數,把67插入到他後面,得到:186 67 64 103,現在由5個數變為4個數了,繼續:186 131 103,現在k=2(別忘了,設A[-1]和A[n]等於正無窮大)234 186,最後得到420。最後的答案呢?就是各次合併的重量之和,即420+234+131+67=852。 基本思想是通過樹的最優性得到一個節點間深度的約束,之後證明操作一次之後的解可以和原來的解一一對應,並保證節點移動之後他所在的深度不會改變。具體實現這個演算法需要一點技巧,精髓在於不停快速尋找最小的k,即維護一個“2-遞減序列”樸素的實現的時間複雜度是O(n*n),但可以用一個平衡樹來優化,使得最終複雜度為O(nlogn)。 GarsiaWachs演算法優化+小細節優化:1 #include <fstream> 2 #include <iostream> 3 #include <cstdio> 4 #include <cstring> 5 #include <cstdlib> 6 #include <cmath> 7 using namespace std; 8 9 const int N = 205; 10 const int INF = 0x7fffffff; 11 12 int stone[N]; 13 int n,t,ans; 14 15 void combine(int k) 16 { 17 int tmp = stone[k] + stone[k-1]; 18 ans += tmp; 19 for(int i=k;i<t-1;i++) 20 stone[i] = stone[i+1]; 21 t--; 22 int j = 0; 23 for(j=k-1;stone[j-1] < tmp;j--) 24 stone[j] = stone[j-1]; 25 stone[j] = tmp; 26 while(j >= 2 && stone[j] >= stone[j-2]) 27 { 28 int d = t - j; 29 combine(j-1); 30 j = t - d; 31 } 32 } 33 34 int main() 35 { 36 //freopen("D:\\input.in","r",stdin); 37 while(~scanf("%d",&n)) 38 { 39 for(int i=1;i<=n;i++) 40 scanf("%d",stone+i); 41 stone[0]=INF; 42 stone[n+1]=INF-1; 43 t = 3; 44 ans = 0; 45 for(int i=3;i<=n+1;i++) 46 { 47 stone[t++] = stone[i]; 48 while(stone[t-3] <= stone[t-1]) 49 combine(t-2); 50 } 51 while(t > 3) combine(t-1); 52 printf("%d\n",ans); 53 } 54 return 0; 55 }0ms
小細節在於把數列前後加兩個值INF和INF-1。這樣就不需要每次判別上下界。至於-1,組合堆的最後一步裡體現。
相關推薦
石子合併問題(直線版)
題目: 石子合併(一) 時間限制:1000 ms | 記憶體限制:65535 KB 難度:3 描述 有N堆石子排成一排,每堆石子有一定的數量。現要將N堆石子併成為一堆。合併的過程只能每次將相鄰的兩堆石子堆成一堆,每次合併花費的代價為這兩堆石子的和,經過N-1次合併後成為一
HRBUST 1818/1819 石子合併問題--直線版 /圓形版(經典動態規劃)
石子合併問題(直線版) 一條直線上擺放著一行共n堆的石子。現要將石子有序地合併成一堆。規定每次只能選相鄰的兩堆合併成新的一堆,並將新的一堆石子數記為該次合併的得分。請編輯計算出將n堆石子合併成一堆的最小得分和將n堆石子合併成一堆的最大得分。 Input 輸入有多組測試資
[NOI1995]石子合併(區間DP)
題目連結: [NOI1995]石子合併 思路: 區間DP經典例題,可以把前n-1堆石子一個個移到第n個後面,那樣環就變成了線,即現在有2*n-1堆石子需要合併。 程式碼: #include <iostream> #include
【區間dp*2】洛谷 P1880 [NOI1995]石子合併 (推導過程) +尼克的任務(還沒寫)
emmmmm給自己設定了一個習慣界限。其實每次看到他們在做啥啥啥而我都大三都現在了 就會感覺很內傷-、- 不過演算法還是要寫的吧 別寫太多而已... 為了防止腦袋空空 也為了防止一天不知道幹什麼 ================================== &
【動態規劃】石子合併 (ssl 2863)
石 子 合 並
石子合併問題--直線版 HRBUST
一條直線上擺放著一行共n堆的石子。現要將石子有序地合併成一堆。規定每次只能選相鄰的兩堆合併成新的一堆,並將新的一堆石子數記為該次合併的得分。請編輯計算出將n堆石子合併成一堆的最小得分和將n堆石子合併成一堆的最大得分。 Input 輸入有多組測試資料。 每組第一行為n(n
石子合併 (區間DP)
一.試題在一個園形操場的四周擺放N堆石子(N≤100),現要將石子有次序地合併成一堆。規定每次只能選相鄰的兩堆合併成新的一堆,並將新的一堆的石子數,記為該次合併的得分。編一程式,由檔案讀入堆數N及每堆的石子數(≤20),①選擇一種合併石子的方案,使得做N-1次合併,得分
nyoj737—石子合併(一)(區間DP)
描述 有N堆石子排成一排,每堆石子有一定的數量。現要將N堆石子併成為一堆。合併的過程只能每次將相鄰的兩堆石子堆成一堆,每次合併花費的代價為這兩堆石子的和,經過N-1次合併後成為一堆。求出總的代價最小值。 輸入 有多組測試資料,輸入到檔案結束
石子合併(NOI1995)題解
題目描述 在一個圓形操場的四周擺放N堆石子,現要將石子有次序地合併成一堆.規定每次只能選相鄰的2堆合併成新的一堆,並將新的一堆的石子數,記為該次合併的得分。 試設計出1個演算法,計算出將N堆石子合併成1堆的最小得分和最大得分. 輸入輸出格式 輸入格式: 資料的第1行試正整數N,1≤N≤100,表示有
nyoj 737 石子合併(一)
#include<string.h> #include<stdio.h> #include<algorithm> using namespace std; #define INF 0x3fffffff int a[205],dp[205][205],sum[205]; in
石子合併(一) 時間限制:1000 ms | 記憶體限制:65535 KB 難度:3 描述 有N堆石子排成一排,每堆石子有一定的數量。現要將N堆石子併成為一堆。合併的過程只能每次將相鄰的兩堆
#include <stdio.h>#include <iostream>//狀態轉移方程:m(i, j) = min(m(i,k),m(k + 1,j)) + sum(i, j)(i <= k < j) #include <str
nyoj 737 石子合併(一) 【區間dp】
石子合併(一) 時間限制:1000 ms | 記憶體限制:65535 KB 難度:3 描述 有N堆石子排成一排,每堆石子有一定的數量。現要將N堆石子併成為一堆。合併的過程只能每次
石子合併(NOI1995)
F: 石子合併(NOI1995) 時間限制: 1 Sec 記憶體限制: 128 MB 題目描述 在操場上沿一直線排列著 n堆石子。現要將石子有次序地合併成一堆。規定每次只能選相鄰的兩堆石子合併成新
動態規劃 java 石子問題(直線)
一.石子問題與動態規劃的矩陣相乘問題類似,剛開始我在陣列邊界上出現了小問題,讓我知道了,陣列的邊界一定到精確,<還是<=,n還是n+1,都要精益求精; 二 .還有就是在i到j石子之和處出現問題,1.sum[j]-sum[i-1] 2.sum[j
石子合併(一) 區間dp
石子合併(一) 題目描述: 有N堆石子排成一排,每堆石子有一定的數量。現要將N堆石子併成為一堆。合併的過程只能每次將相鄰的兩堆石子堆成一堆,每次合併花費的代價為這兩堆石子的和,經過N-1次合併後成為一堆。求出總的代價最小值。 輸入描述: 有多組測試資料,輸入到
石子合併(四邊形優化第一題)
滿足兩個性質 1、區間包含的單調性:如果對於 i≤i'<j≤j',有 w(i',j)≤w(i,j'),那麼說明w具有區間包含的單調性。(可以形象理解為如果小區間包含於大區間中,那麼小區間的w值不超過大區間的w值) 2、四邊形不等式:如果對於 i≤i'<j≤
石子合併(一)
描述 有N堆石子排成一排,每堆石子有一定的數量。現要將N堆石子併成為一堆。合併的過程只能每次將相鄰的兩堆石子堆成一堆,每次合併花費的代價為這兩堆石子的和,經過N-1次合併後成為一堆。求出總的代價最小值。輸入有多組測試資料,輸入到檔案結束。每組測試資料第一行有一個整數n,
nyoj 737 石子合併(一) 區間動規
描述 有N堆石子排成一排,每堆石子有一定的數量。現要將N堆石子併成為一堆。合併的過程只能每次將相鄰的兩堆石子堆成一堆,每次合併花費的代價為這兩堆石子的和,經過N-1次合併後成為一堆。求出總的代
NYOJ 石子合併(一)經典區間DP
石子合併(一) 時間限制:1000 ms | 記憶體限制:65535 KB 難度:3 描述 有N堆石子排成一排,每堆石子有一定的數量。現要將N堆石子併成為一堆。合併的過程只能每次將相鄰的兩堆石子堆成一堆,每次合併花費的代價為這兩堆石子的和,經過N-1次
石子合併(三) 環形合併
題目描述 在一個園形操場的四周擺放N堆石子,現要將石子有次序地合併成一堆.規定每次只能選相鄰的2堆合併成新的一堆,合併的花費為這相鄰兩堆之和 試設計出1個演算法,計算出將N堆石子合併成1堆的最小花費. 輸入輸出格式 輸入格式: 資料的第1行試正整數N,