区间DP
来源:互联网 发布:网络英语教育 编辑:程序博客网 时间:2024/06/06 01:20
区间DP
区间dp就是在一系列的区间中搞特一些子dp,昂。
这玩意儿还得用具体的题目来说。
下面展示一些模型。
石子合并
题目链接:codevs石子合并
分析
这道题目跟合并果子很像啊!但是这道题目只能合并相邻的两堆石子。怎么合并的总得分最大呢,就要保证每一次合并后,得分都最大。符合最优性原则,可以用我们神奇的DP做。
这道题目就是典型的一个区间DP。我们可以知道,每一个区间的最优解都是由更小的区间的最优解合并而来,所以我们划分阶段的依据就是区间长度。再枚举起点,算出终点,进行计算。
code
#include<bits/stdc++.h>#define maxn 350#define INF 2000000000using namespace std;inline int read(){ int num=0; bool flag=true; char c; for(;c>'9'||c<'0';c=getchar()) if(c=='-') flag=false; for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar()); return flag ? num : -num;}//快读,背好就行。int n,a[maxn],f[maxn][maxn],sum[maxn];/*f[i][j]表示区间[I,j]的石头最小得分(最优解)。sum是一个前缀和数组,很好用。a数组是原数据。*/int main(){ n=read(); memset(f,0,sizeof(f)); //初始化 for(int i=1;i<=n;i++) { a[i]=read(); sum[i]=sum[i-1]+a[i];//处理前缀和 } for(int len=2;len<=n;len++) //枚举长度 for(int i=1;i<=n-len+1;i++) //枚举起点 { int j=i+len-1; //求出终点 int temp=INF; for(int k=i;k<j;k++)//枚举所有的合并点。 temp=min(temp,f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);//这就是我们著名的区间dp著名状态转移方程。 f[i][j]=temp;//求出最优解。 } printf("%d\n",f[1][n]);//输出 return 0; }
石子合并++
题目链接:luogu石子合并
分析
这道题目跟上一题唯一的不一样就是这里的石头是环状排列的,这意味着我们需要进行一点不一样的预处理——断环为链。
方法就是——扩大两倍(看了代码就理解了)。
code
#include<bits/stdc++.h>#define maxn 350#define INF 2000000000using namespace std;inline int read(){ int num=0; bool flag=true; char c; for(;c>'9'||c<'0';c=getchar()) if(c=='-') flag=false; for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar()); return flag ? num : -num;}//快读int n,a[maxn*2],sum[maxn*2];//a,原数组。sum:前缀和数组int f1[maxn*2][maxn*2],f2[maxn*2][maxn*2];//f1:放最小值;f2:放最大值int main(){ n=read(); for(int i=1;i<=n;i++) { a[i]=read(); a[n+i]=a[i]; }//著名的断环为链 for(int i=1;i<=2*n;i++) sum[i]=sum[i-1]+a[i]; //当然两倍之后,前缀和也要搞两倍 for(int len=2;len<=n;len++) for(int i=1;i<=2*n-len+1;i++) //同上一题 { int j=i+len-1; int mx=-INF,mi=INF; for(int k=i;k<j;k++) { mx=max(mx,f2[i][k]+f2[k+1][j]+sum[j]-sum[i-1]); mi=min(mi,f1[i][k]+f1[k+1][j]+sum[j]-sum[i-1]); } f1[i][j]=mi; f2[i][j]=mx;//一样的骚操作 } int ansmin=INF,ansmax=-INF; for(int i=1;i<=n;i++) { ansmin=min(ansmin,f1[i][i+n-1]); ansmax=max(ansmax,f2[i][i+n-1]); } /*由于是原题是环,意味着我们这里有不一样的操作, 我们需要枚举不同的断开点*/ printf("%d\n%d",ansmin,ansmax); return 0; }
能量项链
题目链接:luogu能量项链
分析
这道题目跟合并果子实在是太像了。只要处理一下计算收益的部分就OK了。
#include<bits/stdc++.h>#define maxn 120*2#define INF 2000000000using namespace std;inline int read(){ int num=0; bool flag=true; char c; for(;c>'9'||c<'0';c=getchar()) if(c=='-') flag=false; for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar()); return flag ? num : -num;}int f[maxn][maxn],n;struct node { int head,tail;}a[maxn];int main(){ n=read(); for(int i=1;i<=n;i++) a[i].head=a[i+n].head=read(); for(int i=1;i<=2*n-1;i++) a[i].tail=a[i+1].head; a[2*n].tail=a[1].head; for(int len=2;len<=n;len++) for(int i=1;i<=2*n-len+1;i++) { int j=i+len-1; int temp=-INF; for(int k=i;k<j;k++) { temp=max(temp,f[i][k]+f[k+1][j]+a[i].head*a[k].tail*a[j].tail); } f[i][j]=temp; } int ans=-INF; for(int i=1;i<=n;i++) ans=max(ans,f[i][i+n-1]); printf("%d",ans); return 0; }
阅读全文
0 0