1. 程式人生 > 實用技巧 >P1220 關路燈

P1220 關路燈

題目描述

某一村莊在一條路線上安裝了 \(n\) 盞路燈,每盞燈的功率有大有小(即同一段時間內消耗的電量有多有少)。老張就住在這條路中間某一路燈旁,他有一項工作就是每天早上天亮時一盞一盞地關掉這些路燈。

為了給村裡節省電費,老張記錄下了每盞路燈的位置和功率,他每次關燈時也都是儘快地去關,但是老張不知道怎樣去關燈才能夠最節省電。他每天都是在天亮時首先關掉自己所處位置的路燈,然後可以向左也可以向右去關燈。開始他以為先算一下左邊路燈的總功率再算一下右邊路燈的總功率,然後選擇先關掉功率大的一邊,再回過頭來關掉另一邊的路燈,而事實並非如此,因為在關的過程中適當地調頭有可能會更省一些。

現在已知老張走的速度為 \(1m/s\)

,每個路燈的位置(是一個整數,即距路線起點的距離,單位:\(m\))、功率(\(W\)),老張關燈所用的時間很短而可以忽略不計。

請你為老張編一程式來安排關燈的順序,使從老張開始關燈時刻算起所有燈消耗電最少(燈關掉後便不再消耗電了)。

輸入格式

第一行是兩個數字 \(n\)(表示路燈的總數)和 \(c\)(老張所處位置的路燈號);

接下來 \(n\) 行,每行兩個資料,表示第 \(1\) 盞到第 \(n\) 盞路燈的位置和功率。資料保證路燈位置單調遞增。

輸出格式

一個數據,即最少的功耗(單位:\(J\)\(1J=1W\times s\))。

輸入輸出樣例

輸入
5 3
2 10
3 20
5 20
6 30
8 10
輸出
270

說明/提示

樣例解釋

此時關燈順序為 \(3\) \(4\) \(2\) \(1\) \(5\)

資料範圍

\(1\le n\le50,1\le c\le n\)

Solution

一道需要分析題目性質的區間 \(dp\) 的題目,思維考驗的很到位。
首先分析一下為什麼本題的做法是區間 \(dp:\)
注意到老張是從一個點開始往左邊或右邊走,且經過的點的燈一定都是關的,所以說,[ \(a_i<a_j<a_k\),且 \(a_i\)\(a_k\) 的位置的燈已經關了,而 \(a_j\) 這個位置的燈沒關 ] 這種情況這是不可能的。因為無論老張是從 \(a_i\)

走到 \(a_k\) 還是從 \(a_k\) 走到 \(a_i\),他都一定會經過 \(a_j\),那麼他選擇關上 \(a_j\) 位置的燈一定是最優的。
這麼說來,老張所關的燈一定是在一個連續的區間,這就啟發我們利用區間 \(dp\) 來解決這道題。
還有一個性質:
老張每關一盞燈,他一定處於這個區間的端點。
這一點很好理解,因為老張想要關燈,他就要讓這個區間不斷地向左向右擴充套件,那麼他一定是要走出原來的區間的,這樣他所在的位置就一定是區間端點。
假設老張已經擴充套件的區間是 \([l,r]\),且老張在區間的左端點 \(l\) 處,那麼他下一步有兩種選擇:\(①\)關掉第 \(l-1\) 盞燈;\(②\)關掉第 \(r+1\) 盞燈。
如果他要去關第 \(l-1\) 盞燈,他需要花 \(a_{l}-a_{l-1}\) 的時間,在此期間,\(1\)~\(l-1,r+1\)~\(n\) 的燈一直亮著,那麼代價就是 \((a_l-a_{l-1})*(S[1][l-1]+S[r+1][n])\)
如果他要去關第 \(r+1\) 盞燈,他需要花 \(a_{r+1}-a_l\) 的時間,在此期間,\(1\)~\(l-1,r+1\)~\(n\) 的燈一直亮著,那麼代價就是 \((a_{r+1}-a_{l})*(S[1][l-1]+S[r+1][n])\)
\((S[i][j]\) 表示 \(i\)~\(j\) 的路燈 \(1s\) 所消耗的電能和\()\)
由此可見,老張在區間左右端點的位置不同,所影響的花費時間不同,進而影響不同的代價。
所以我們要再開一維來記錄老張此時是在區間的左端點還是右端點。
那麼狀態也就隨之出來了。

狀態設定

\(dp[l][r][0/1]\) 表示老張已經將 \([l,r]\) 內的燈關了,且老張此時是在左端點\((\)\(0\)表示\()\)還是在右端點\((\)\(1\)表示\()\)

狀態轉移

我是用填表法來做的。
考慮 \(dp[l][r][0]\) 由什麼轉移到:
既然老張是在區間的左端點,即 \(a_l\) 處,那麼也就說明第 \(l\) 盞燈是老張剛關上的,那麼上一步的區間應該是 \([l+1,r]\)
如果老張原來是在 \(a_{l+1}\) 處,要走 \(a_{l+1}-a_l\) \(s\),代價為 \((a_{l+1}-a_l)*(S[1][l]+S[r+1][n])\)
如果老張原來是在 \(a_r\) 處,要走 \(a_r-a_l\) \(s\),代價為 \((a_r-a_l)*(S[1][l]+S[r+1][n])\)
兩者取 \(\min\) 即可:
\(dp[l][r][0]=\min(dp[l+1][r][0]+(a_{l+1}-a_l)*(S[1][l]+S[r+1][r]),dp[l+1][r][1]+(a_r-a_l)*(S[1][l]+S[r+1][n]))\)

同理得:

\(dp[l][r][1]=\min(dp[l][r-1][0]+(a_r-a_l)*(S[1][l-1]+S[r][n]),dp[l][r-1][1]+(a_r-a_{r-1})*(S[1][l-1]+S[r][n]))\)

那麼這個題就做完了。

Code

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<ctime>
#define db double
#define ll long long
using namespace std;
inline int read()
{
	char ch=getchar();
	int a=0,x=1;
	while(ch<'0'||ch>'9')
    {
    	if(ch=='-') x=-x;
    	ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		a=(a<<1)+(a<<3)+(ch^48);
		ch=getchar();
	}
	return a*x;
}
const int N=200;
int n,m;
int a[N],b[N],S[N][N],dp[N][N][2];
int dis(int x,int y)     //求第x盞燈和第y盞燈之間的距離 
{
	return abs(a[x]-a[y]);
}
int main()
{
	n=read();m=read();
	for(int i=1;i<=n;i++) 
	{
		a[i]=read();b[i]=read();
	}
	for(int i=1;i<=n;i++)
	    for(int j=i;j<=n;j++)
	        for(int k=i;k<=j;k++)
	            S[i][j]+=b[k];      //預處理第i~j盞燈1s所消耗的電能 
	memset(dp,0x3f,sizeof(dp));
	dp[m][m][0]=dp[m][m][1]=0;      //邊界條件:老張一開始在第m盞燈,所以代價為0 
	for(int len=2;len<=n;len++)
	{
		for(int l=1;l+len-1<=n;l++)
		{
			int r=l+len-1;
			dp[l][r][0]=min(dp[l+1][r][0]+dis(l+1,l)*(S[1][l]+S[r+1][n]),dp[l+1][r][1]+dis(r,l)*(S[1][l]+S[r+1][n]));
			dp[l][r][1]=min(dp[l][r-1][0]+dis(l,r)*(S[1][l-1]+S[r][n]),dp[l][r-1][1]+dis(r-1,r)*(S[1][l-1]+S[r][n]));
		}
	}
	printf("%d\n",min(dp[1][n][0],dp[1][n][1]));  //最後老張在左端右端都行,取min 
	return 0;
}