Asurudo
2019-08-25 17:17:14
这题没混直接发
瞅了一眼其他题解,大致与我的做法相同,不过貌似都是毫无疑问"一眼得":区间dp,然后dp状态从天而降,转移方程横空出现,不禁让我想起了我的小白时代,看着这些转移方程两眼一摸黑,所以我决定多说两句也发一篇题解。
题目大意就是一位老大爷跑左跑右去关灯,这些灯每个单位时间都会产生能量耗费,但老大爷没有一响指关掉所有灯的能力,问把这些灯全关完最少需要耗费多少能量。
首先需要明确的是,这位大爷不可能"三过路灯而不关"。也就是说将这些电灯放在一个数轴上,题目所给的路灯位置就作为坐标,所有已经关掉的灯在数轴上的位置形成的必然是一段连续的区间,因为路过一盏灯不关一定不划算(即路过不关灯耗费的能量一定比路过顺手关灯耗费的能量多),关灯不需要时间嘛,自然也不会产生多余的能量耗费,这一点很好理解。
现在我们已经达成了一个共识:大爷已经关掉的灯在数轴上是连续的。有了这个共识以后,我们再思考:既然关掉的灯是连续的,那此时大爷在哪儿?很显然,大爷一定也在这段连续的区间里,否则如果他不在这段区间里,则在外面的一盏灯下,那么区间就不连续了,因此大爷一定在这段连续的区间里。具体的说,大爷要么在这段区间的最左边,要么在区间的最右边,因为大爷是从某一位置开始拓展他的关灯区间,这个区间只能从两边进行拓展,大爷从一开始就在区间端点,每一步动作都是为了关掉下一盏灯,自然不可能在关完一盏灯以后傻傻站在区间中心随着时间流逝。
那么对于某一个关灯区间
如果你现在说,我们不要大爷的位置了,只要区间左右端点值可不可以?答案是不可以。为什么?因为当你从子问题
如果你现在又说了,我觉得这三个要素确定的状态(区间两端点+大爷左/右)还不过瘾,我还想知道到底关了多少盏灯了,那么我们考虑,加进来这个要素组成四要素,最后这还是不是个子问题?还能不能推敲出更大问题的最优解?可以,当然可以,但是既然可要可不要,那我们为什么要加进来呢?
因此,我们最后敲定,区间两端点+大爷左/右这三要素为我们的状态,问题又接踵而至,如果我们现在知道了
我们考虑大爷一次关一盏灯,也就存在两种大情况(四种小情况):
转移就由此诞生。
怎么诞生的?如果你能从这个子问题推到更大的问题,那更大问题的最优解就是所有这些子问题转移来的,都汇总过来以后考虑怎么得出最优解,可能你使用
转移说完了,最后来说说怎么转移,我们首先考虑预处理一个数组
小问题最优花费+时间*亮着的灯的单位时间花费 = 大问题最优花费,这时候你就知道
dp[l][r][left] = min( dp[l+1][r][left] + (pos(l+1) - pos(l)) * (sum[1][l] + sum[r+1][n]) , dp[l+1][r][right] + (pos(r) - pos(l)) * (sum[1][l] + sum[r+1][n]))
dp[l][r][right] = min(dp[l][r-1][left] + (pos(r) - pos(l)) * (sum[1][l-1] + sum[r][n]) , dp[l][r-1][right] + (pos(r) - pos(r-1)) * (sum[1][l-1] + sum[r][n]))
最后考虑一下边界处理。大爷一开始向左走的话,则有
一开始向右走,则有
说到这里,如果你还是不能理解
附赠
#include <bits/stdc++.h>
#define _for(i,a,b) for(int i = (a);i < b;i ++)
#define _rep(i,a,b) for(int i = (a);i > b;i --)
#define INF 0x3f3f3f3f
#define MOD 1000000007
typedef long long ll;
using namespace std;
inline ll read()
{
ll ans = 0;
char ch = getchar(), last = ' ';
while(!isdigit(ch)) last = ch, ch = getchar();
while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
if(last == '-') ans = -ans;
return ans;
}
inline void write(ll x)
{
if(x < 0) x = -x, putchar('-');
if(x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
struct lamp
{
int pos;
int power;
};
int n,c;
lamp a[53];
int dp[53][53][2];
int sum[53][53];
int main()
{
n = read(), c = read();
_for(i,1,n+1)
a[i].pos = read(),a[i].power = read();
_for(i,1,n+1)
{
sum[i][i] = a[i].power;
_for(j,i+1,n+1)
sum[i][j] = sum[i][j-1]+a[j].power;
}
memset(dp,0x3f,sizeof(dp));
if(c>=2)
dp[c-1][c][0] = (a[c].pos-a[c-1].pos)*(sum[1][c-1]+sum[c+1][n]);
if(c<=n-1)
dp[c][c+1][1] = (a[c+1].pos-a[c].pos)*(sum[1][c-1]+sum[c+1][n]);
_for(len,3,n+1)
_for(l,1,n-len+2)
{
int r = l+len-1;
dp[l][r][0] = min(dp[l+1][r][0]+(a[l+1].pos-a[l].pos)*(sum[1][l]+sum[r+1][n])
,dp[l+1][r][1]+(a[r].pos-a[l].pos)*(sum[1][l]+sum[r+1][n]));
dp[l][r][1] = min(dp[l][r-1][0]+(a[r].pos-a[l].pos)*(sum[1][l-1]+sum[r][n])
,dp[l][r-1][1]+(a[r].pos-a[r-1].pos)*(sum[1][l-1]+sum[r][n]));
}
write(min(dp[1][n][0],dp[1][n][1]));
return 0;
}