动态规划--区间dp

news2025/7/23 15:24:33

区间dp

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

题目列表:

(1)石子合并

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

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

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

#include<iostream>
#include<algorithm>
#include<cstring>
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的石子无法合并,所以消耗的体力就是0

	for (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 <R; 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<iostream>
#include<cstring>
#include<algorithm>
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 <<endl<< ans;
	return 0;
}

(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<iostream>
#include<cstring>
#include<algorithm>
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;i<n*2;i++)   //长度为2的区间代表一个石子,不能合并,从定义出发那就意味着没有能量。
        f[i][i+1]=0;
    
    for(int len=3;len<=n+1;len++) //枚举长度,什么是n+1,因为3个石子的区间长度是4
    {
        for(int l=1;l+len-1<=n*2;l++)  //枚举左端点
        {
            int r=l+len-1;  //右端点
            f[l][r]=-INF;
            for(int k=l+1;k<r;k++)  //区间分段求最大,分段要注意细节,根据事实来分,这里的细节是:长度为1的区间没有意义  2 3 4 5
            {
                f[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<<res;
    return 0;
}

(4)加分二叉树

在这里插入图片描述

#include<iostream>
#include<cstring>
#include<algorithm>
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<<f[1][n]<<endl;
    dfs(1,n);
    return 0;
}

(5)凸多边形的划分

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

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
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 <n; i++)  //两个点不能连接三角形,所以权值为0
		f[i][i + 1] = 0;
	for (int len = 3; len <= n;len++)  //枚举区间   ,,为什么不要做环形处理,要看定义,这里的定义是区间[i,j]内划分三角形的最大价值
		for (int l = 1; l + len - 1 <= n; l++)  //枚举左端点
		{
			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<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<cstdio>

using namespace std;
typedef long long ll;
const int N=51,M=35,INF=0x3f3f3f3f;

vector<int>f[N][N];  //状态表示:f[i][j]表示一个一个一维数组,存放一个高精度的值,他的意义是左端点是i,右端点是j的封闭的多边形的最大价值
int w[N];   //存储点
int n;


void pre(vector<int>& s,int t)   //先假设一个数,边看这个数边来模拟就不容易出错。example:1234
{
    //将t放入s
    while(t)
    {
        s.push_back(t%10);
        t/=10;
    }
}

bool cmp(vector<int>&a ,vector<int>&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;
        else
            return false;
    }
    else
    {
        //说明两个数组长度一样
        for(int i=a.size()-1;i>=0;i--)  //逆序比较
        {
            if(a[i]!=b[i])  //高位比较,如果a[i]大于b[i],则a>b
                return a[i]>b[i];
        }
        return true;//相等的情况
    }
      
}

vector<int> mul(vector<int>&a,ll b)//123 12
{
    vector<int>c;
    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;
}

vector<int>add(vector<int>&a,vector<int>&b)
{
    vector<int>c;
    ll t=0;
    for(int i=0;i<a.size()||i<b.size();i++)
    {
        if(i<a.size())t+=a[i];
        if(i<b.size())t+=b[i];
        c.push_back(t%10);
        t/=10;
    }
    while(t)
    {
        c.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个点才可以构成回路
    vector<int>temp;
    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<int>(M,9);
        
            for(int k=i+1;k<j;k++)  //k能枚举到i+1到j-1,
            {
                //目的是求f[i,k]+f[k,j]+w[i]*w[k]*w[j];
                //先求乘法
                temp.clear();
                temp.push_back(w[i]);  //高精度相加,那么是3个数组的加法,需要将具体的数字都放在数组里面,先将第一个数放在数组里面
                //cout<<temp[0]<<temp[1]<<temp[2]<<endl;
                temp=mul(temp,w[k]);
                temp=mul(temp,w[j]);   //到这里就完成了乘法了
                temp=add(temp,f[i][k]);
                temp=add(temp,f[k][j]);
                //到这里就完成了加法和乘法的累和
                if(cmp(f[i][j],temp))  //如果temp小于等于f[i][j]
                    f[i][j]=temp;
            }
            
        }
    }
    for(int i=f[1][n].size()-1;i>=0;i--)
        cout<<f[1][n][i];
    return 0;
}

(6)棋盘分割

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

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
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;i<x2;i++)  //横切
    {
        t=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;i<y2;i++)  //竖切
    {
        t=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见。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/16892.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1.2 Android 5.0 的特点

和其他版本相比&#xff0c; Android 5.0 的突出特性如下所示。 &#xff08;1&#xff09;全新的 Material 界面设计 Android 5.0 Lollipop 界面设计的灵感来源于自然、 物理学 以及基于打印效果的粗体、图标化的设计&#xff0c;换句话说&#xff0c;它的设 计是一种基于高品…

智慧建筑BIM解决方案-最新全套文件

智慧建筑BIM解决方案-最新全套文件一、建设背景为什么要发展智慧建筑二、思路架构三、建设方案智慧建筑建设时应考虑下面3个方面&#xff1a;1、减少耗能&#xff0c;促进资源利用效率2、优化工作和生活环境3、确保运营安全可靠四、获取 - 智慧建筑BIM全套最新解决方案合集一、…

m超外差单边带接收机的simulink仿真

目录 1.算法概述 2.仿真效果预览 3.MATLAB部分代码预览 4.完整MATLAB程序 1.算法概述 超外差是利用本地产生的振荡波与输入信号混频&#xff0c;将输入信号频率变换为某个预先确定的频率的方法。这种方法是为了适应远程通信对高频率、弱信号接收的需要&#xff0c;在外差原…

基于springboot在线玩具商城交易平台的设计与实现

随着科技创新不断突破玩具界限&#xff0c;特别是随着智能时代到来&#xff0c;电子游戏的兴起对传统玩具行业带来了冲击&#xff0c;智能玩具应运而生&#xff0c;成为新产品方向。智能玩具受消费者青睐&#xff0c; 随着电子商务的发展&#xff0c;其在我国的经济地位越来越…

spring boot酒店会员点餐系统毕业设计源码072005

Springboot酒店会员点餐系统 摘 要 进入21世纪以来&#xff0c;计算机有了迅速的发展。计算机应用、信息技术全面渗透到了人类社会的各个方面&#xff0c;信息化已成为世界经济和社会发展的大趋势。―企业的管理也从人工操作变得更加自动化、智能化和高效化。如果复杂的工作光靠…

PMP大家都是怎么备考的?使用什么工具可以分享一下吗?

这里分享PMP理论中的4个工具&#xff0c;在人生管理和项目管理中是通用的。所有的工具&#xff0c;只有在对的时间&#xff0c;用在对的地方&#xff0c;才能真正指导实践。 项目经理应符合PMI人才三角。分别为&#xff1a;技术项目管理&#xff1b;领导力&#xff1b;战略和…

腾讯云服务器后台重装后需要配置的一些东西

1、adduser 用户名&#xff08;创建普通用户&#xff09; 2、passwd 用户名&#xff08;给普通用户设置密码&#xff09; 3、userdel -r 用户名&#xff08;删除普通用户&#xff09; 4、修改/etc/sudoers文件&#xff08;给普通用户可以提权的机会&#xff09; 5、sudo yum in…

Hive——Hive常用内置函数总结

✅作者简介&#xff1a;最近接触到大数据方向的程序员&#xff0c;刚入行的小白一枚 &#x1f34a;作者博客主页&#xff1a;皮皮皮皮皮皮皮卡乒的博客 &#x1f34b;当前专栏&#xff1a;Hive学习进阶之旅 &#x1f352;研究方向&#xff1a;大数据方向&#xff0c;数据汇聚&a…

vdsm:添加接口调试demo

目录 添加API接口 2.添加api方法 3.Vdsm-api.yml添加参数 暴露jsonrpc接口&#xff1a; 需要重启vdsmd vdsm-client 调试 本文通过添加一个配置ovs全局参数的接口 添加API接口 文件路径&#xff1a;API.py 2.添加api方法 文件路径&#xff1a;network/api.py 3.Vdsm-ap…

4.2——Node.js的npm和包

目录初识node.jsnode.js的安装和查看版本使用node命令对js文件运行窗口的快捷键fs 文件系统模块fs.readFile() 方法写入文件fs.writeFile()案例——考试成绩整理路径问题path 路径模块路径拼接path.join()获取路径中的文件名path.basename()获取路径中的文件扩展名path.extname…

用Python的Django框架来制作一个RSS阅读器

Django带来了一个高级的聚合生成框架&#xff0c;它使得创建RSS和Atom feeds变得非常容易。 什么是RSS&#xff1f; 什么是Atom&#xff1f; RSS和Atom都是基于XML的格式&#xff0c;你可以用它来提供有关你站点内容的自动更新的feed。 了解更多关于RSS的可以访问 http://www…

[附源码]SSM计算机毕业设计足球队管理系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[附源码]java毕业设计企业记账系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

QT 发布文章遇到问题解决方案

提供了两种可以发布 Qt 程序的方案&#xff0c;建议使用第二种直接生成对应的文件&#xff0c;直接打包就可以 1. 手动复制需要的文件到运行目录下 我们写完 QT 程序当然是要发布或者发给其他需要用到的人&#xff0c;由于找不到Qt6Core.dll,无法继续执行代码,打开 realease …

Python基础语法

一、字面量&#xff1a;在代码中&#xff0c;被写下来的固定的值 二、注释 /增加代码的可读性 单行注释 #空格注释文字内容 &#xff08;加空格只是规范&#xff09;#右边 多行注释 一对三个双引号 """注释内容""" 三、变量 -->程序运行时…

Linux基础内容(12)—— 程序地址空间

目录 1.误区和它的由来 2.虚拟地址的证明 3.虚拟地址的实现 1.虚拟空间的解释 2.操作系统管理和规划虚拟空间 3.虚拟地址与物理地址的联系 4.多进程的虚拟地址解释 5.磁盘中可执行文件的地址 6.进程地址空间出现的原因 接上面内容 Linux基础内容&#xff08;11&#…

在排序数组中查找元素的第一个和最后一个位置 - 力扣中等

在排序数组中查找元素的第一个和最后一个位置 题目链接 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计并实现时间…

2022 年 10 月 NFT 报告

Sept. 13, 2022, Daniel Data Source: October 2022 NFT Report (ENG) 10 月对于区块链来说是一个相对沉闷的月份&#xff0c;没有巨大的市场波动、项目启动或融资轮次。 由于宏观环境依然严峻&#xff0c;NFT 市场自夏末以来继续停滞不前。 从上个月的报告开始&#xff0c;F…

精准配置无线接入点发射功率

目录 1、为什么需要调节无线接入点的发射功率 2、无线接入点发送功率配置原则 2.1 802.11管理帧发射功率对接入行为影响 2.2 802.11数据帧发射功率对接入质量的影响 2.3 802.11管理帧、数据帧发射功率协调原则 1、数据帧发射功率务必大于等于管理帧发射功率 2、高频射频…

DOX-Poloxamer/DBCO-PEG-DOX 阿霉素修饰泊洛沙姆/二苯基环辛-聚乙二醇-阿霉素的探究

小编这里分享了DOX-Poloxamer/DBCO-PEG-DOX 阿霉素修饰泊洛沙姆/二苯基环辛-聚乙二醇-阿霉素的探究&#xff0c;和小编一起来看&#xff01; DBCO&#xff08;二苯并环辛炔&#xff09;是一种环炔烃&#xff0c;可以通过在水溶液中通过应变促进的1,3-偶极环加成反应与叠氮化物反…