【努力刷力扣】第三十九天 --- 洛谷P220关路灯
引言
本人初次尝试写博客,希望各位看官大佬多多包容
有错误希望巨巨们提出来,我一定会及时改正,谢谢大家
在自己最好的年纪,找到了未来的目标
还有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。