P1220 關路燈
題目描述
某一村莊在一條路線上安裝了 \(n\) 盞路燈,每盞燈的功率有大有小(即同一段時間內消耗的電量有多有少)。老張就住在這條路中間某一路燈旁,他有一項工作就是每天早上天亮時一盞一盞地關掉這些路燈。
為了給村裡節省電費,老張記錄下了每盞路燈的位置和功率,他每次關燈時也都是儘快地去關,但是老張不知道怎樣去關燈才能夠最節省電。他每天都是在天亮時首先關掉自己所處位置的路燈,然後可以向左也可以向右去關燈。開始他以為先算一下左邊路燈的總功率再算一下右邊路燈的總功率,然後選擇先關掉功率大的一邊,再回過頭來關掉另一邊的路燈,而事實並非如此,因為在關的過程中適當地調頭有可能會更省一些。
現在已知老張走的速度為 \(1m/s\)
請你為老張編一程式來安排關燈的順序,使從老張開始關燈時刻算起所有燈消耗電最少(燈關掉後便不再消耗電了)。
輸入格式
第一行是兩個數字 \(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\)
這麼說來,老張所關的燈一定是在一個連續的區間,這就啟發我們利用區間 \(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;
}