[好题][区间dp]关路灯 洛谷P1220

题目描述

某一村庄在一条路线上安装了 n 盏路灯,每盏灯的功率有大有小(即同一段时间内消耗的电量有多有少)。老张就住在这条路中间某一路灯旁,他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯。

为了给村里节省电费,老张记录下了每盏路灯的位置和功率,他每次关灯时也都是尽快地去关,但是老张不知道怎样去关灯才能够最节省电。他每天都是在天亮时首先关掉自己所处位置的路灯,然后可以向左也可以向右去关灯。开始他以为先算一下左边路灯的总功率再算一下右边路灯的总功率,然后选择先关掉功率大的一边,再回过头来关掉另一边的路灯,而事实并非如此,因为在关的过程中适当地调头有可能会更省一些。

现在已知老张走的速度为 1m/s,每个路灯的位置(是一个整数,即距路线起点的距离,单位:m)、功率(W),老张关灯所用的时间很短而可以忽略不计。

请你为老张编一程序来安排关灯的顺序,使从老张开始关灯时刻算起所有灯消耗电最少(灯关掉后便不再消耗电了)。

输入格式

第一行是两个数字 n(表示路灯的总数)和 c(老张所处位置的路灯号);

接下来 n 行,每行两个数据,表示第 1 盏到第 n 盏路灯的位置和功率。数据保证路灯位置单调递增。

输出格式

一个数据,即最少的功耗(单位:J,1J=1W×s)。

输入输出样例

输入 #1

5 3
2 10
3 20
5 20
6 30
8 10

输出 #1

270  

说明/提示

样例解释

此时关灯顺序为 3 4 2 1 5

数据范围

1≤n≤50,1≤c≤n。

题意: 有n盏路灯,每盏都有一个位置x[i]和功率w[i],老张有一个初始位置s,问老张从s关闭所有路灯时浪费的最少功率是多少。

分析: 看上去像是区间dp,于是设dp[i][j]表示老张关完[i, j]区间内路灯浪费的功率,但是这样会发现少记录了老张的位置,如果没有老张位置信息是没法更新更大的区间的,因此多开一维状态dp[i][j][2],由于老张关完区间内的灯时只会在左端点或者右端点处,所以第三维记录老张在左端点(取0)还是右端点(取1)。注意一下这里的[i, j]需要包含老张的初始位置s,否则dp值是无意义的。

之后就可以写出状态转移方程了,dp[i][j][0] = min(dp[i][j][0], min(t1, t2)),其中t1 = min(dp[l+1][r][0]+W(), dp[l+1][r][1]+W()),t2 = min(dp[l][r-1][0]+W(), dp[l][r-1][1]+W()),这里的W()函数就是从当前位置走到对应位置所浪费的功率,用前缀和*时间就可以得到,具体见下方代码。dp[i][j][1]的状态转移方程也是类似的,这里不再赘述了。

状态初始化我选择手动初始化区间长度为1和2的情况,这样更加保险。最后更新完dp数组后取一个min就可以得到答案了。

具体代码如下: 

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <string>
#define pii pair<int, int>
#define x first
#define w second
using namespace std;

int dp[55][55][2], n, s, sum[55];
pii L[55];

int W(int l, int r, int t){
	return t*(sum[l-1]+sum[n]-sum[r]);
}

signed main()
{
	cin >> n >> s;
	for(int i = 1; i <= n; i++){
		scanf("%d%d", &L[i].x, &L[i].w);
		sum[i] = sum[i-1]+L[i].w;
	}
	memset(dp, 0x3f, sizeof dp);
	//初始化区间长度为1和2的情况 
	dp[s][s][0] = dp[s][s][1] = 0;
	if(s+1 <= n){
		dp[s][s+1][0] = W(s, s, L[s+1].x-L[s].x)+W(s, s+1, L[s+1].x-L[s].x);
		dp[s][s+1][1] = W(s, s, L[s+1].x-L[s].x);
	}
	if(s-1 >= 1){
		dp[s-1][s][0] = W(s, s, L[s].x-L[s-1].x);
		dp[s-1][s][1] = W(s, s, L[s].x-L[s-1].x)+W(s-1, s, L[s].x-L[s-1].x);
	}
	for(int len = 3; len <= n; len++){
		for(int l = 1; l+len-1 <= n; l++){
			int r = l+len-1;
			if(s > r || s < l) continue;//老张不在的区间无意义
			int t1 = min(dp[l+1][r][0]+W(l+1, r, L[l+1].x-L[l].x), dp[l+1][r][1]+W(l+1, r, L[r].x-L[l].x)); 
			int t2 = min(dp[l][r-1][0]+W(l, r-1, L[r].x-L[l].x)+W(l, r, L[r].x-L[l].x), dp[l][r-1][1]+W(l, r-1, L[r].x-L[r-1].x)+W(l, r, L[r].x-L[l].x));
			dp[l][r][0] = min(dp[l][r][0], min(t1, t2));
			t1 = min(dp[l+1][r][0]+W(l+1, r, L[l+1].x-L[l].x)+W(l, r, L[r].x-L[l].x), dp[l+1][r][1]+W(l+1, r, L[r].x-L[l].x)+W(l, r, L[r].x-L[l].x));
			t2 = min(dp[l][r-1][0]+W(l, r-1, L[r].x-L[l].x), dp[l][r-1][1]+W(l, r-1, L[r].x-L[r-1].x));
			dp[l][r][1] = min(dp[l][r][1], min(t1, t2));
		}
	}
	cout << min(dp[1][n][0], dp[1][n][1]);

    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值