淼淼刷力扣(特别篇区间DP2)

引言

本人初次尝试写博客,希望各位看官大佬多多包容
有错误希望巨巨们提出来,我一定会及时改正,谢谢大家
在自己最好的年纪,找到了未来的目标
还有1年奋斗刷题,明年去面试实习,加油!
博主最近要参加竞赛,所以暂时调整训练计划。

题目要求:

在这里插入图片描述
在这里插入图片描述

前序知识

区间DP就是四个过程:找好多少种状态,根据题意赋初始值,找状态转移方程,找寻答案,那么接下来我将从这四个方面为大家解析这两道题。
(这样理解DP问题更直观,但可能与教科书上的讲解存在出入,姑且按照这个方法来,如果有错误请读者老爷们指出,我一定改正)

整体思路

1、定状态

这里很容易证明,以下图为例,我在3复活,我在关5的同时顺手关了4的消耗一定小于先关5再回头关掉4的消耗,所以在考虑状态时 k = 0 就是在区间最左面 k = 1 就是在区间最右面。
在这里插入图片描述
所以 i,j表示区间 k 表示人的位置
(其实只要是给一个链上的问题或者环上的DP问题就是一定有两个状态 i,j 用来表示区间,剩下的就得自己分析了)

2、初始化

因为按照题意,人是在复活点处开始关灯而不是起点处,一复活就关掉自己位置的灯,但是我们由小区间扩展到大区间时,是从头开始的,为了让他开始关灯从有效位置开始且我们要功耗最小,所以"非复活点的功耗设置成无穷,复活点处设置为0",在条件逼迫下他就会挑能耗最小的起点处关灯。

3、找转移

找转移的时候一定记住,我们就只找与这个状态最接近的上一个小状态

状态1

mn[i][j][0] = min(mn[i][j][0], mn[i][j - 1][0] + get_dis(i, j)*(get_power(i, j - 1) + get_power(i, j)));
解释:想把i到j都关了,那么它上一种状态可能是i到j-1关完了,就只需要反手把j关了就行,关之前人站在i,关完了之后去i。从i去j,i到j-1是灭着的,从j回i,i到j是灭着的。

状态2

mn[i][j][0] = min(mn[i][j][0], mn[i][j - 1][1] + get_dis(j - 1, j)*get_power(i, j - 1) + get_dis(i, j)*get_power(i, j));
解释:想把i到j都关了,那么它上一种状态可能是i到j-1关完了,就只需要反手把j关了就行,关之前人站在j-1,关完了之后去i。从j-1去j,i到j-1是灭着的,从j回i,i到j是灭着的。

状态3

mn[i][j][0] = min(mn[i][j][0], mn[i + 1][j][0] + get_dis(i, i + 1)*get_power(i + 1, j));
解释:想把i到j都关了,那么它上一种状态可能是i+1到j关完了,就只需要反手把i关了就行,关之前人站在i+1,关完了之后去i。从i+1去i,i+1到j是灭着的,关完之后就在i

状态4

mn[i][j][0] = min(mn[i][j][0], mn[i + 1][j][1] + get_dis(i, j)*get_power(i + 1, j));
解释:想把i到j都关了,那么它上一种状态可能是i+1到j关完了,就只需要反手把i关了就行,关之前人站在j,关完了之后去i。从j去i,i+1到j是灭着的,关完之后就在i

状态5

mn[i][j][1] = min(mn[i][j][1], mn[i][j - 1][0] + get_dis(i, j)*get_power(i, j - 1));
解释:想把i到j都关了,那么它上一种状态可能是i到j-1关完了,就只需要反手把j关了就行,关之前人站在i,关完了之后去j。从i去j,i到j-1是灭着的,关完之后就在j

状态6

mn[i][j][1] = min(mn[i][j][1], mn[i][j - 1][1] + get_dis(j - 1, j)*get_power(i, j - 1));
解释:想把i到j都关了,那么它上一种状态可能是i到j-1关完了,就只需要反手把j关了就行,关之前人站在j-1,关完了之后去j。从j-1去j,i到j-1是灭着的,关完之后就在j

状态7

mn[i][j][1] = min(mn[i][j][1], mn[i + 1][j][0] + get_dis(i, i + 1)*get_power(i + 1, j) + get_dis(i, j)*get_power(i, j));
解释:想把i到j都关了,那么它上一种状态可能是i+1到j关完了,就只需要反手把i关了就行,关之前人站在i+1,关完了之后去j。从i+1去i,i+1到j是灭着的,从i回到j,i到j是灭着的.

状态8

mn[i][j][1] = min(mn[i][j][1], mn[i + 1][j][1] + get_dis(i, j)*(get_power(i + 1, j) + get_power(i, j)));
解释:想把i到j都关了,那么它上一种状态可能是i+1到j关完了,就只需要反手把i关了就行,关之前人站在j,关完了之后去j。从j去i,i+1到j是灭着的,从i回到j,i到j是灭着的.

4、找结果

这就是一长条灯,不是环,所以当区间到了1-n时就行了,但是人最后站在0,还是1,对结果有影响,所以就取二者中较小的即可。

具体代码(内附注释)

注:因为这一串路灯我每个小区间都要访问的到,所以双层for循环处外层初始值为1

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 305;
const int INF = 1 << 30;
int mn[N][N][2];//存状态
int dis[N] = { 0 };//存距离(因为输入时已经是前缀和形式,所以便于求距离)
int power[N] = { 0 };//存能耗
int n, c;//路灯数,复活点
int get_dis(int i, int j) {//获取距离,用前缀和
	return dis[j] - dis[i];
}
int get_power(int i, int j) {//i到j的灯是关着的,计算剩余区间功耗,因为关灯区间我们是知道的
	//之所以知道关灯区间是因为我们就是从一个点出发,将一个区间关灯
	//我们的目的是为了关灯,所以枚举的区间都是要去关灯的
	//所以用总能耗减去关掉的灯的能耗
	return power[n] - (power[j] - power[i - 1]);//用前缀和算出关掉的灯能耗,用总的减去这些就是所求
}
int main() {
	cin >> n >> c;
	for (int i = 1; i <= n; i++) {//初始化
		cin >> dis[i] >> power[i];
		power[i] += power[i - 1];
		mn[i][i][0] = INF;//初始值
		mn[i][i][1] = INF;
		if (i == c) {
			mn[i][i][0] = 0;
			mn[i][i][1] = 0;
		}
	}
	for (int len = 1; len < n; len++) {//为了让全部区间得以被访问
		for (int i = 1, j; i + len <= n; i++) {
			j = i + len;
			mn[i][j][0] = INF;//初始值
			mn[i][j][1] = INF;
			//八种状态转移
			mn[i][j][0] = min(mn[i][j][0], mn[i][j - 1][0] + get_dis(i, j)*(get_power(i, j - 1) + get_power(i, j)));
			mn[i][j][0] = min(mn[i][j][0], mn[i][j - 1][1] + get_dis(j - 1, j)*get_power(i, j - 1) + get_dis(i, j)*get_power(i, j));
			mn[i][j][0] = min(mn[i][j][0], mn[i + 1][j][0] + get_dis(i, i + 1)*get_power(i + 1, j));
			mn[i][j][0] = min(mn[i][j][0], mn[i + 1][j][1] + get_dis(i, j)*get_power(i + 1, j));

			mn[i][j][1] = min(mn[i][j][1], mn[i][j - 1][0] + get_dis(i, j)*get_power(i, j - 1));  
			mn[i][j][1] = min(mn[i][j][1], mn[i][j - 1][1] + get_dis(j - 1, j)*get_power(i, j - 1));
			mn[i][j][1] = min(mn[i][j][1], mn[i + 1][j][0] + get_dis(i, i + 1)*get_power(i + 1, j) + get_dis(i, j)*get_power(i, j));
			mn[i][j][1] = min(mn[i][j][1], mn[i + 1][j][1] + get_dis(i, j)*(get_power(i + 1, j) + get_power(i, j)));
		}
	}
	cout << min(mn[1][n][1], mn[1][n][0]);//找答案
}

(所有代码均已运行无误)

经测试,该代码运行情况是:

在这里插入图片描述

时间复杂度:O(n^2)

Sum Up

其实当我们遇见这一类-给一定的区间或者环,让我们求最优解(最大或者最小)问题,我们其中的一种思路就是用区间DP
1、围绕着四大点展开分析,其中状态转移方程设计的总体
在这里插入图片描述
这个就是模板,也就是说我们这个大区间的最优要找小区间的最优,加上本身区间的代价即可,剩下的根据具体问题分析即可。
2、在分解区间时,模板为
for (int len = 1; len < n; len++) {
for (int i = 1, j; i + len <= n; i++) {
如果是环形的,则外层初始是2,如果是线性的,外层初始为1。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JLU_LYM

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值