#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<stack> #include<map> #include<cmath> #include<algorithm> using namespace std; const int N=410; int f[N][N]; int n; struct node{ int x,y; }a[N]; int main() { cin>>n; for(int i=1;i<=n;i++) { cin>>a[i].x; a[i-1].y=a[i].x; } a[n].y=a[1].x; for(int i=1;i<n;i++) a[n+i]=a[i];//使其成爲一條鏈,方便操做 //i~i+n就能夠當作一個完整的鏈 memset(f,0,sizeof(f)); //f[i][j]表示合併第i~j個石子的最優值 for(int k=1;k<=n;k++)//枚舉長度 for(int i=1;i<2*n-k;i++)//從第i個石子開始斷開, //那麼i~i+n就是一條線 for(int j=i;j<i+k;j++) //在i~i+k中任意選一點j做爲分界點,而後就能夠分紅 //i~j和j+1~i+k這兩段 //首先這兩段裏面的都合併起來,而後最後(i,j)(j+1,i+k)端點序號爲這兩個的 //珠子在盡心合併 { f[i][i+k]=max(f[i][i+k],f[i][j]+f[j+1][i+k]+a[i].x*a[j].y*a[i+k].y); } int ans=0; for(int i=1;i<=n;i++) ans=max(ans,f[i][i+n-1]); cout<<ans<<endl; return 0; }
在一個圓形操場的四周擺放 \(N\) 堆石子,現要將石子有次序地合併成一堆.規定每次只能選相鄰的\(2\)堆合併成新的一堆,並將新的一堆的石子數,記爲該次合併的得分。node
試設計出一個算法,計算出將 \(N\) 堆石子合併成 \(1\) 堆的最小得分和最大得分。ios
區間dp,因此咱們最多見的方法是狀態轉移方程設置爲區間的形式算法
最多見的作法爲在一個大的區間內找兩個區間並進行合併,求最大值數組
這個題咱們經過數據能夠發現,當區間\([i,j]\)中兩個小的區間\([i,k],[k+1,j]\)合併時,它此次合併的得分正好爲第\(i\)堆到第\(j\)堆的石子的總個數
因此咱們設\(s_i\)爲前\(i\)堆石子的前綴和spa
咱們能夠設置\(f[i][j]表示從第\)i\(堆到第\)j$堆合併成爲\(1\)堆時的區間最值,能夠獲得如下的狀態轉移方程式翻譯
#include<iostream> #include<queue> #include<stack> #include<cstdio> #include<cstring> #include<queue> #include<map> #include<algorithm> using namespace std; const int N=5e2+9; int a[N]; int n; int s[N];//前綴和 int fmax[N][N],fmin[N][N]; int main() { cin>>n; for(int i=1;i<=n;i++) { cin>>a[i]; a[i+n]=a[i]; } for(int i=1;i<=n*2;i++) s[i]=s[i-1]+a[i]; //memset(fmin,0x3f3f3f,sizeof(fmin)); for(int l=1;l<n;l++) for(int i=1,j=i+l;(i<n+n)&&(j<n+n);i++,j=i+l) { fmin[i][j]=0x3f3f3f3f; for(int k=i;k<j;k++) { fmax[i][j]=max(fmax[i][j],fmax[i][k]+fmax[k+1][j]+(s[j]-s[i-1])); //兩邊i~k和k+1~j,最後一次合併的時候加起來獲得的的分數正好是 //i~j的和 fmin[i][j]=min(fmin[i][j],fmin[i][k]+fmin[k+1][j]+(s[j]-s[i-1])); } } int amax=0,amin=0x3f3f3f3f; for(int i=1;i<=n;i++) { amax=max(amax,fmax[i][i+n-1]); amin=min(amin,fmin[i][i+n-1]); } cout<<amin<<endl; cout<<amax<<endl; return 0; return 0; }
(一個由於翻譯而WA的「毒瘤」題)
給定一個長度爲\(n\)的區間,在區間內相鄰的且數字大小相同的兩個數字能夠合併的到一個比它\(+1\)數字
詢問能夠合併成的最大數值爲多少設計
一個線性區間dp,咱們依舊是在區間內作處理指針
在一個區間內,枚舉長度,並在這個區間內找一個分割點,是這個點兩邊的數值是相等的,而後進行大小比較code
咱們能夠設\(f[i][j]\)爲區間\([i,j]\)內的合併出來的最大值ci
由此能夠獲得狀態轉移方程(狀態能夠根據須要靈活變化,此方程取\(j\)爲分界點)
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<stack> #include<map> #include<algorithm> #include<cmath> using namespace std; const int N=5e2+9; int f[N][N]; int num[N]; int n; int main() { cin>>n; for(int i=1;i<=n;i++) { cin>>num[i]; f[i][i]=num[i]; } int ans=0; for(int k=1;k<=n;k++)//枚舉區間長度 for(int i=1;i+k<=n;i++)//確保右端點在範圍內 for(int j=i;j<i+k;j++)//保證分割的界限在範圍內 { if(f[i][j]==f[j+1][i+k])//判斷兩邊是否相等 f[i][i+k]=max(f[i][i+k],f[i][j]+1); //能夠改爲 f[i][i+k]=max(f[i][i+k],f[j+1][i+k]+1); ans=max(f[i][i+k],ans);//在過程當中找答案,節省時間 } cout<<ans<<endl; return 0; }
#include<cstdio> #include<cstring> #include<queue> #include<stack> #include<map> #include<algorithm> #include<iostream> using namespace std; const int N=1e2+9; int f[N][N]; char s[N]; int main() { cin>>(s+1); memset(f,0x3f3f3f3f,sizeof(f)); for(int i=1;i<=strlen(s+1);i++) f[i][i]=1;//開始能夠被塗一次//bingo for(int k=1;k<=strlen(s+1);k++)//枚舉區間 { for(int i=1;i+k<=strlen(s+1);i++) { if(s[i]==s[i+k])//兩端點相等,因此咱們就在首次塗色的時候多塗上一層, //看看是塗到左邊端點花費少仍是塗到右邊端點花費少 f[i][i+k]=min(f[i+1][i+k],f[i][i+k-1]); else for(int j=i;j<i+k;j++) f[i][i+k]=min(f[i][i+k],f[i][j]+f[j+1][i+k]); //若是兩個端點同樣,那麼就是兩塊區間相加求最小值 //由於當你左右兩邊不同是,必定會左右均刷一次, //而後對於中間的,就看一看是否有同樣的就能夠 //dp枚舉能夠考慮到上述狀況 } } cout<<f[1][strlen(s+1)]<<endl; return 0; }
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; const int N=209; bool f[N][N][5],can[5][5][5];//f表示區間[i,j]能夠經過k轉化過來 //can表示i,j能夠經過k轉化過來 int le[5];//存長度 char s[N],c[5]; int ques(char s) { if(s=='W') return 1; if(s=='I') return 2; if(s=='N') return 3; if(s=='G') return 4; } int main() { for(int i=1;i<=4;i++) cin>>le[i]; for(int i=1;i<=4;i++) { for(int j=1;j<=le[i];j++) { cin>>c; can[i][ques(c[0])][ques(c[1])]=true;//表示i能夠從這倆轉化過來 } } cin>>(s+1); int len=strlen(s+1); for(int i=1;i<=len;i++) f[i][i][ques(s[i])]=true; for(int k=1;k<=len;k++) for(int i=1;i+k<=len;i++) for(int j=i;j<i+k;j++) for(int z=1;z<=4;z++)//枚舉能夠代替z1,z2的數 for(int z1=1;z1<=4;z1++)//枚舉z1 for(int z2=1;z2<=4;z2++)//枚舉z2 { if(f[i][j][z1]&&f[j+1][i+k][z2]&&can[z][z1][z2]) //這個方程表示若是區間[i,j]能夠被z1表示 //而且區間[j+1,i+k]能夠被z2表示 //同時z能夠與z1,z2轉化 //那麼[i,i+k]這個區間就能夠被z來表示 f[i][i+k][z]=true; } bool flag=0; if(f[1][len][1]) {flag=1,cout<<'W';}; if(f[1][len][2]) {flag=1,cout<<'I';}; if(f[1][len][3]) {flag=1,cout<<'N';}; if(f[1][len][4]) {flag=1,cout<<'G';}; if(!flag) cout<<"The name is wrong!"<<endl; return 0; }
設\(f[i][j][k]\)表示第\(i\)行,狀態爲第\(i\)行,狀態爲\(j\)時,前\(i\)行的一共放了\(k\)個國王的方案數
獲得如下解題思路
#include<iostream> #include<cstdio> #include<string> #include<queue> #include<stack> #include<map> #include<algorithm> #define int long long using namespace std; const int N=11; const int M=2009; int n,num; int cnt;//狀態的指針 int situ[M];//可用的狀態 int sum[M];//求每個狀態所包含的1的數量 int f[N][(1<<N)][N*N];//表示第i行,狀態是j,放置了k個棋子時的狀態... void search(int he,int gs,int pif)//表示狀態,表示1的個數,表示當前爲第幾位 { if(pif>=n) { situ[++cnt]=he; sum[cnt]=gs; return; } search(he,gs,pif+1);//這個就是表示當前位數沒有選,要選擇與他相鄰的位數 search (he+(1<<pif),gs+1,pif+2);//當前爲要選的第pif位,因此就在第pif位上標上個1; //表示在這個地方有一個國王 } signed main() { cin>>n>>num; search(0,0,0); for(int i=1;i<=cnt;i++) f[1][i][sum[i]]=1; //第二惟爲狀態的下標不是狀態 for(int i=2;i<=n;i++) for(int j=1;j<=cnt;j++)//枚舉當前的狀態 for(int k=1;k<=cnt;k++)//枚舉上一個的狀態 { if(situ[j]&situ[k])continue; if(situ[j]&(situ[k]<<1)) continue; if((situ[j]<<1)&situ[k]) continue; for(int l=num;l>=sum[j];l--) f[i][j][l]+=f[i-1][k][l-sum[j]]; } int ans=0; for(int i=1;i<=cnt;i++) ans+=f[n][i][num]; cout<<ans<<endl; return 0; }