1. 程式人生 > >P1880 [NOI1995]石子合並 區間DP

P1880 [NOI1995]石子合並 區間DP

特意 算法 define ace pla img 格式 click color

題目描述

在一個圓形操場的四周擺放N堆石子,現要將石子有次序地合並成一堆.規定每次只能選相鄰的2堆合並成新的一堆,並將新的一堆的石子數,記為該次合並的得分。

試設計出1個算法,計算出將N堆石子合並成1堆的最小得分和最大得分.

輸入輸出格式

輸入格式:

數據的第1行試正整數N,1≤N≤100,表示有N堆石子.第2行有N個數,分別表示每堆石子的個數.

輸出格式:

輸出共2行,第1行為最小得分,第2行為最大得分.

輸入輸出樣例

輸入樣例#1: 復制
4
4 5 9 4
輸出樣例#1: 復制
43
54


特意去學習了區間dp 發現是區間dp入門題:石子劃分的加強版 (當然 這題也是入門題) 對比那題 只是把鏈變成了環

可以得到區間dp的方程:
f[i][j] = max(f[i][k] + f[k+1][j] + 合並付出的代價) 這裏的代價是 i到j的所有石子合

如果對dp順序沒有加以設計的話 很容易寫出
i=1 to 10,j=1 to 10,k=1 to 9.當i=1,j=5,k=3時,顯然狀態f[k+1][j]沒有結果。 f[4][5]的結果為i=4 j=5時求出的
所以區間dp對執行順序是有考究的:
枚舉j-i(也就是len),並在j-i中枚舉k。這樣,就能保證地推的正確。

技術分享圖片
#include<bits/stdc++.h>
using namespace std;
//input
#define
rep(i,x,y) for(int i=(x);i<=(y);++i) #define RI(n) scanf("%d",&(n)) #define RII(n,m) scanf("%d%d",&n,&m); #define RIII(n,m,k) scanf("%d%d%d",&n,&m,&k) #define RS(s) scanf("%s",s) #define LL long long #define REP(i,N) for(int i=0;i<(N);i++) #define CLR(A,v) memset(A,v,sizeof A) /////////////////////////////////
/ #define N 300 #define inf 0x3f3f3f3f int a[N]; int dp1[N][N];//max int dp2[N][N];//min int s[N]; int main() { int n; RI(n); rep(i,1,n) RI(a[i]),a[i+n]=a[i]; rep(i,1,2*n) s[i]=s[i-1]+a[i]; rep(len,1,n-1) { for(int i=1,j=i+len;i<=2*n&&j<=2*n;i++,j=i+len ) { dp2[i][j]=inf;//註意求最小值的初始化 for(int k=i;k<j;k++) { dp1[i][j]=max(dp1[i][j],dp1[i][k]+dp1[k+1][j]+s[j]-s[i-1] ); dp2[i][j]=min(dp2[i][j],dp2[i][k]+dp2[k+1][j]+s[j]-s[i-1] ); } } } int ans1=0; int ans2=inf; rep(i,1,n) { ans1=max(ans1,dp1[i][i+n-1]);//註意減一 ans2=min(ans2,dp2[i][i+n-1]); } cout<<ans2<<endl<<ans1; return 0; }
View Code

學了區間dp 又引出了一大堆區間dp的題目 果然學的越多不會的越多QwQ





P1880 [NOI1995]石子合並 區間DP