动态规划--区间dp
创始人
2024-01-31 06:23:25
0

区间dp

  • 题目列表:
    • (1)石子合并
    • (2)环形石子合并
    • (3)能量项链
    • (4)加分二叉树
    • (5)凸多边形的划分
    • (6)棋盘分割

题目列表:

(1)石子合并

在复习石子合并之前,为了直接进入专题“区间dp“,做一个区间dp的基础题,这个题目具有代表性:(题目用到了前缀和,前缀和看这里: )

  1. 使用了区间dp的模板
  2. 清晰理解区间dp的思想

区间dp:将问题分为若干区间,不断解决小区间,最终延展到整个问题的区间,即:一个问题的范围是一个很大的区间,那么通过不断解决小区间,延伸到解决大区间
在这里插入图片描述

#include
#include
#include
using namespace std;const int N = 310,INF=0x3f3f3f3f;int f[N][N];  //f[i][j]表示区间i到j的石子合并的最小代价。那么最终问题的解就是f[1][n]表示第一个石子到最后一个石子合并的最小代价
//dp问题三部曲:
//(1)集合定义,即dp[i][j]表示什么,是否合理  (2)dp初始化,由于dp要往后面推,那么dp就要有初始值,0生1,1生2,3生3,3生万物
//(3)dp的状态转移,即当前dp的下一步要解决什么事情(也叫状态转移方程组)int w[N];  //存储石子的重量
int pre[N];   //石子堆的前缀和
int n;
int main()
{cin >> n;for (int i = 1; i <= n; i++){cin >> w[i];pre[i] = pre[i - 1] + w[i];}for (int i = 1; i <= n; i++)f[i][i] =0;      //区间为1的石子无法合并,所以消耗的体力就是0for (int len = 2; len <= n; len++)  //枚举区间长度{for (int L = 1; L + len - 1 <= n; L++)//(L+len-1)表示长度为len的区间的右端点{int R = L + len - 1;     //右端点f[L][R] = INF;  //因为要求最小值,先预先设置一个最大值for (int k = L; k f[L][R] = min(f[L][R], f[L][k] + f[k + 1][R] + pre[R] - pre[L - 1]); //f[L][R]的最小价值等于两个部分的最小值加上整个区间的重量}}}cout << f[1][n];return 0;
}

(2)环形石子合并

在这里插入图片描述

这题于上一个题的唯一区别在于:这是环形,首尾的石子可以先合并。那么就使用一种”化曲为直“的思想。将环形结构拉直变成线性结构:
将数组扩大两倍,后面部分复制一遍数据即可模拟环形结构。

#include
#include
#include
using namespace std;const int N = 410,INF=0x3f3f3f3f;int f[N][N];   //f[i][j]表示区间[i,j]的石子合并消耗的最小体力。
int g[N][N];   //g[i][j]表示区间[i,j]的石子合并消耗的最大体力
int pre[N];   //pre[i]表示前i个石子的重量和
int w[N];   //石子重量   
int n;
int main()
{cin >> n;for (int i = 1; i <= n; i++)cin >> w[i];for (int i = n + 1; i <= 2 * n; i++)  //化曲为直的操作w[i] = w[i - n];for (int i = 1; i <= 2 * n; i++){pre[i] = pre[i - 1] + w[i];}//初始化for (int i = 1; i <= n * 2; i++)f[i][i] = g[i][i] = 0;for (int len = 2; len <= n; len++){for (int l = 1; l + len - 1 <= 2 * n; l++){int r = l + len - 1;f[l][r] = INF;   //求最小值给最大值g[l][r] = -INF;   //求最大值给最小值for (int k = l; k < r; k++){f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + pre[r] - pre[l - 1]);g[l][r] = max(g[l][r], g[l][k] + g[k + 1][r] + pre[r] - pre[l - 1]);}}}int res = INF; int ans = -INF;for (int i=1; i + n - 1 <= 2 * n; i++){res = min(res, f[i][i + n - 1]);ans = max(ans, g[i][i + n - 1]);}cout << res <

(3)能量项链

在这里插入图片描述
大致题意:
每个珠子都有两面,一面一个值。有n个珠子,首尾相连,且任意相邻的两个珠子的邻接面的值是一样的,两颗珠子可以合并为一个珠子,且释放能量。
给一个示例:

(1,2) (2,3)(3,4)(4,1) 4颗珠子,首尾相连且满足相邻的值一样。有点像矩阵相乘
其中一种组合方法:
(1,2)和(2,3)组合变成(1,3),释放能量1x2x3
(1,3)和(3,4)组合变成(1,4),释放能量1x3x4
(1,4)和(4,1)组合变成(1,1),释放能力1x4x1
没有珠子可以合并了,发现一共合并3次,释放能量:6+12+4=22.但是不是最优解就不知道了。因为从不同的珠子为起点来合并答案都不一样.

#include
#include
#include
using namespace std;const int N=1100,INF=0x3f3f3f3f;
typedef long long ll;
ll f[N][N];   //f[i][j]表示区间[i,j]的石子合并的最大能量,最后的答案是f[1][n+1]
int w[N*2];
int n;int main()
{cin>>n;for(int i=1;i<=n;i++){scanf("%d",&w[i]);w[i+n]=w[i];}//dp初始化for(int i=1;ifor(int l=1;l+len-1<=n*2;l++)  //枚举左端点{int r=l+len-1;  //右端点f[l][r]=-INF;for(int k=l+1;kf[l][r]=max(f[l][r],f[l][k]+f[k][r]+w[l]*w[r]*w[k]);}}}ll res=0;for(int i=1;i+n<=2*n;i++)  //枚举左端点求最大值{res=max(res,f[i][i+n]);  //搞不清这个是n还是n+1的时候举个例子。比如n==3,  1 2 3 1 2 3.要选取4个数,保证首尾相同}cout<

(4)加分二叉树

在这里插入图片描述

#include
#include
#include
using namespace std;const int N=50,INF=0x3f3f3f3f;int w[N];
int f[N][N];  //f[i][j]表示区间[i,j]的子树的最大值
int path[N][N];  //path[i][j]表示子树[i,j]的根节点int n;void dfs(int l,int r)  //求前序遍历
{ if(l>r)return;int k=path[l][r];printf("%d ",k);dfs(l,k-1);dfs(k+1,r);
}
int main()
{cin>>n;for(int i=1;i<=n;i++)cin>>w[i];//初始化for(int i=1;i<=n;i++){f[i][i]=w[i];   //一个节点的最大能量就是当前节点值path[i][i]=i;  //叶子节点没有子树,的根节点就是自己}//dp的思路是:枚举所有长度的子树,且求出字典序最小的根节点for(int len=1;len<=n;len++)   //枚举长度{for(int l=1;l+len-1<=n;l++)  //枚举左端点{int r=l+len-1;  //右端点f[l][r]=-INF;for(int root=l;root<=r;root++)  //枚举根节点{int left=root==l?1:f[l][root-1];int right=root==r?1:f[root+1][r];int temp=left*right+w[root];if(l==r)temp=w[root];if(temp>f[l][r])  //因为原先的树是12345,所以保证字典序的最小就是保证每次的根节点最小就好了。因为四前序遍历{f[l][r]=temp;path[l][r]=root;}}}}cout<

(5)凸多边形的划分

在这里插入图片描述
这个题目不用高精度只能过几个点,所以用高精度
先上一个不用高精度的,能过三个数据:

#include
#include
#include
#include
using namespace std;const int N = 100,INF=0x3f3f3f3f;long long int f[N][N];  //f[i][j]表示区间[i,j]的权值最大值
int w[N];    //每个点的权值
int n;
int main()
{cin >> n;for (int i = 1; i <= n; i++)cin >> w[i];//初始化for (int i = 1; i int r = l + len - 1;if (len == 3){f[l][r] = w[l] * w[l+1] * w[r];continue;}f[l][r] = INF;for (int k = l + 1; k < r; k++){f[l][r] = min(f[l][r], f[l][k] + f[k][r]+w[l]*w[r]*w[k]);}}cout << f[1][n];return 0;
}

下面是高精度:

#include
#include
#include
#include
#includeusing namespace std;
typedef long long ll;
const int N=51,M=35,INF=0x3f3f3f3f;vectorf[N][N];  //状态表示:f[i][j]表示一个一个一维数组,存放一个高精度的值,他的意义是左端点是i,右端点是j的封闭的多边形的最大价值
int w[N];   //存储点
int n;void pre(vector& s,int t)   //先假设一个数,边看这个数边来模拟就不容易出错。example:1234
{//将t放入swhile(t){s.push_back(t%10);t/=10;}
}bool cmp(vector&a ,vector&b)  //比较f[l][r]和f[l][k]+f[k][r]+w[l]*w[k]*w[r]哪个小,规定a>b,返回true
{if(a.size()!=b.size()){if(a.size()>b.size())return true;elsereturn false;}else{//说明两个数组长度一样for(int i=a.size()-1;i>=0;i--)  //逆序比较{if(a[i]!=b[i])  //高位比较,如果a[i]大于b[i],则a>breturn a[i]>b[i];}return true;//相等的情况}}vector mul(vector&a,ll b)//123 12
{vectorc;ll t=0;for(int i=0;i<(int)a.size();i++){t+=b*a[i];c.push_back(t%10);t/=10;}while(t){c.push_back(t%10);t/=10;}return c;
}vectoradd(vector&a,vector&b)
{vectorc;ll t=0;for(int i=0;iif(ic.push_back(t%10);t/=10;}return c;}int main()
{cin>>n;for(int i=1;i<=n;i++)cin>>w[i];//初始化dp,dp[i][i+1]=0,因为两条边是不能构成回路的,多边形必须要3条边或者说3个点才可以构成回路vectortemp;for(int len=3;len<=n;len++){for(int i=1;i+len-1<=n;i++)  //枚举左端点{int j=i+len-1;  //右端点//因为要求f[l][r]的最小值,所以将其先预先设置为无穷大,f[i][j]=vector(M,9);for(int k=i+1;k//目的是求f[i,k]+f[k,j]+w[i]*w[k]*w[j];//先求乘法temp.clear();temp.push_back(w[i]);  //高精度相加,那么是3个数组的加法,需要将具体的数字都放在数组里面,先将第一个数放在数组里面//cout<=0;i--)cout<

(6)棋盘分割

记忆化dp,将棋盘进行分割,分别有两种情况。要么竖着切,要么横切。
然后取一边继续切割。最后回溯的时候取两边的最小值即可

#include
#include
#include
#include
using namespace std;const int N=16;
double f[N][N][N][N][N];  //f[k][x1][y1][x2][y2]表示对棋盘进行了k次划分,得到的左上角是(x1,y1),右下角是(x2,y2)的棋盘的最小分值int n,m=8;
int s[N][N];  //前缀和
double x;double get(int x1,int y1,int x2,int y2)
{double delta=s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1];  //计算当前矩阵的分值和delta=delta-x;return delta*delta;
}double dp(int k,int x1,int y1,int x2,int y2)
{if(f[k][x1][y1][x2][y2]>=0)return f[k][x1][y1][x2][y2];  //记忆化搜索的关键if(k==n) return f[k][x1][y1][x2][y2]=get(x1,y1,x2,y2);   //最后一次不需要继续切了,直接返回当前的矩阵double t=1e9;  for(int i=x1;it=min(t,dp(k+1,x1,y1,i,y2)+get(i+1,y1,x2,y2));  //取上半部分继续切,那么下半部分分值加起来t=min(t,dp(k+1,i+1,y1,x2,y2)+get(x1,y1,i,y2));}for(int i=y1;it=min(t,dp(k+1,x1,y1,x2,i)+get(x1,i+1,x2,y2));  //取左边继续切t=min(t,dp(k+1,x1,i+1,x2,y2)+get(x1,y1,x2,i));}return f[k][x1][y1][x2][y2]=t;
}int main()
{cin>>n;for(int i=1;i<=m;i++)for(int j=1;j<=m;j++)scanf("%d",&s[i][j]);for(int i=1;i<=m;i++)   //二维前缀和for(int j=1;j<=m;j++)s[i][j]=s[i][j]+s[i-1][j]+s[i][j-1]-s[i-1][j-1];memset(f,-1,sizeof f);x=(double)s[m][m]/n;printf("%.3lf",sqrt(dp(1, 1, 1, m, m) / n));return 0;}

好了,区间dp到此为止。明天树形dp见。

相关内容

热门资讯

我的良师益友作文600字 我的良师益友作文600字  无论是身处学校还是步入社会,大家都接触过作文吧,借助作文人们可以反映客观...
收集阳光作文 收集阳光作文  在日常学习、工作抑或是生活中,大家都经常看到作文的身影吧,借助作文可以提高我们的语言...
机遇的作文 机遇的作文600字(精选30篇)  在平日的学习、工作和生活里,大家都跟作文打过交道吧,作文是一种言...
我的好朋友作文300字 我的好朋友作文300字(通用9篇)  无论是在学校还是在社会中,大家都经常接触到作文吧,写作文是培养...
成语故事:纵虎归山 成语故事:纵虎归山  在生活中我们要养成积累成语的意识,对提升自我有益无害。下面请欣赏小编给大家收集...
心中永远的女神作文 心中永远的女神作文  在日常学习、工作抑或是生活中,大家对作文都不陌生吧,作文是人们以书面形式表情达...
记事的作文 关于记事的作文(精选76篇)  在日常学习、工作抑或是生活中,许多人都写过作文吧,作文是从内部言语向...
昆虫备忘录作文 昆虫备忘录作文范文  在日常学习、工作抑或是生活中,大家一定都接触过作文吧,写作文可以锻炼我们的独处...
我心目中的英雄作文650字 我心目中的英雄作文范文650字(精选27篇)  在日常学习、工作或生活中,大家对作文都再熟悉不过了吧...
感恩缺憾作文 感恩缺憾作文  在现实生活或工作学习中,大家都尝试过写作文吧,作文是人们把记忆中所存储的有关知识、经...
愈挫愈勇作文 愈挫愈勇作文愈挫愈勇作文1当我面对挫折的时候人生苦短几十年,我们不得不珍惜自己的生命,但每当自己遇到...
眼里有个UFO作文 眼里有个UFO作文  “下一个演讲,胡夏晖!”“自从《眼里有个UFO》发表以后,本人看了一遍,真有点...
相约在春天作文 相约在春天作文  在日常的学习、工作、生活中,大家对作文都再熟悉不过了吧,借助作文人们可以反映客观事...
写的作文300字 写的作文300字合集8篇  无论在学习、工作或是生活中,大家都不可避免地要接触到作文吧,写作文可以锻...
雏菊的喜与悲 雏菊的喜与悲我所讲的是一种微不足道的小花,它不像杜鹃有端庄的花瓣,也不如牡丹娇艳,它只是淡淡地散发出...
风600字作文 风600字作文 风是隐形的,也是多变的,还是自由的。 风有时很调皮,会舞动着隐形的身子,在树间来回穿...
最新时事热点作文素材 2022最新时事热点作文素材  漫长的学习生涯中,大家最害怕的就是作文了吧?这个时候,作文素材就派上...
100字作文大全五年级 导语:文章的力量是不可估量的,写作可以带来感动是一方面,写作对于我们还有更重要的意义,想要展示自我时...
上课纪律优秀作文 上课纪律优秀作文  关于上课纪律的作文  我们中国一向以礼仪之邦、文明古国著称于世,一直有着崇尚道德...
环卫工人的作文 关于环卫工人的作文(通用7篇)  导语:在我心中,环卫工人是那么热爱劳动,干净,勤劳,因为他们的辛勤...