1. 程式人生 > 實用技巧 >學習記錄--迭代器

學習記錄--迭代器

技術標籤:動態規劃演算法區間dp深度搜索字首和

題目描述

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

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

現在已知老張走的速度為1m/s1m/s,每個路燈的位置(是一個整數,即距路線起點的距離,單位:mm)、功率(WW),老張關燈所用的時間很短而可以忽略不計。

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

輸入格式

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

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

輸出格式

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

輸入輸出樣例

輸入 #1複製

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

輸出 #1複製

270  

說明/提示

樣例解釋

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

資料範圍

1\le n\le501≤n≤50,1\le c\le n1≤c≤n。

方法一:深搜 +剪枝

#include<bits/stdc++.h>

using namespace std;

int a[55],s[55],n,w[55],x,ans=0x3f3f3f3f,m,b[55],h,ss,sum,vis[55];
void dfs(int k,int dep, int ss,int sum){//k 當前位置, dep搜尋的位置, ss計算使用,sum:當前用的時間
    if(k<1 || k>n || sum>ans) return;//假如當前的時間已經比ans還大,停止搜尋
    if(dep>n){
        ans=min(ans,sum);
        return ;
    }
    b[dep-1]=k;//b記錄路徑座標
    for(int i=k-1; i>=1; i--){//往左邊找一個
         if(vis[i]==0){
            vis[i]=1;
            b[dep]=i;
            dfs(i,dep+1,ss-w[b[dep-1]],sum+(ss-w[b[dep-1]])*abs(a[b[dep]]-a[b[dep-1]]));
            vis[i]=0;
            break;
         }
    }
    for(int i=k+1; i<=n; i++){//往右邊找一個
        if(vis[i]==0){
            vis[i]=1;
            b[dep]=i;
            dfs(i,dep+1,ss-w[b[dep-1]],sum+(ss-w[b[dep-1]])*abs(a[b[dep]]-a[b[dep-1]]));
            vis[i]=0;
            break;
         }
    }
}
int main()
{
    cin>>n>>x;
    for(int i=1; i<=n; i++){
       cin>>a[i]>>w[i];
       h+=w[i];
    }
    vis[x]=1;
    dfs(x,2,h,0);
    cout<<ans<<endl;
    return 0;
}
//搜尋方法

方法二:區間dp

狀態:

剛開始想:dp[i][j]表示關掉區間[i,j]所花費的最小功耗。

但是每一個狀態有兩種情況:

1、此時站在左端點(i)

2、此時站在右端點(j)

所以我們再加上一維資訊,dp[i][j][0]關掉區間[i,j],此時站在i點所花費的最小功耗,dp[i][j][1]關掉區間[i,j],此時站在j點所花費的最小功耗.

dp[i][j][0]=min(dp[i+1][j]+ (a[i+1]-a[i])*sum(i+1,j),dp[i+1][j][1]+(a[j]-a[i])*sum(i+1,j) ); 其中sum(i,j)表示i到j的和,使用字首和。

dp[i][j][1]=min(dp[i][j-1][0]+(a[j]-a[i]))*sum(i,j-1), dp[i][j-1][1]+(a[j]-a[j-1])*sum(i,j-1)).

邊界值: dp[x][x][0]=dp[x][x][1]=0; x為老張最初站的位置。

答案:min(dp[1][n][0],dp[1][n][1]);

#include<bits/stdc++.h>

using namespace std;

int a[55],n,w[55],sum[55],dp[55][55][2],x;
int f(int x, int y,int l, int r){
    return (a[x]-a[y])*(sum[n]-sum[r]+sum[l-1]);
}
int main()
{
    cin>>n>>x;
    for(int i=1; i<=n; i++){
       cin>>a[i]>>w[i];
       sum[i]=sum[i-1]+w[i];
    }
    memset(dp,0x3f,sizeof(dp));
    dp[x][x][1]=dp[x][x][0]=0;//區間長度為1,邊界值
    for(int l=2; l<=n; l++){//區間長度為2
       for(int i=1; i+l-1<=n; i++){//區間左端點
         int j=i+l-1;//區間右端
         dp[i][j][0]=min(dp[i+1][j][0]+f(i+1,i,i+1,j),dp[i+1][j][1]+f(j,i,i+1,j));
         dp[i][j][1]=min(dp[i][j-1][0]+f(j,i,i,j-1),dp[i][j-1][1]+f(j,j-1,i,j-1));
       }
    }
    cout<<min(dp[1][n][0],dp[1][n][1])<<endl;
    return 0;
}
//搜尋方法

總結:區間dp,根據題目需要,可以加一維陣列記錄相關資訊。