#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; }