关灯/节能(区间dp)

题目描述
宁智贤得到了一份有趣而高薪的工作。每天早晨她必须关掉她所在村庄的街灯。所有的街灯都被设置在一条直路的同一侧。 宁智贤每晚到早晨5点钟都在晚会上,然后她开始关灯。开始时,她站在某一盏路灯的旁边。 每盏灯都有一个给定功率的电灯泡,因为宁智贤有着自觉的节能意识,她希望在耗能总数最少的情况下将所有的灯关掉。 宁智贤因为太累了,所以只能以1m/s的速度行走。关灯不需要花费额外的时间,因为当她通过时就能将灯关掉。 编写程序,计算在给定路灯设置,灯泡功率以及宁智贤的起始位置的情况下关掉所有的灯需耗费的最小能量。

输入格式
第一行包含一个整数N,2≤N≤1000,表示该村庄路灯的数量。

第二行包含一个整数V,1≤V≤N,表示宁智贤开始关灯的路灯号码。

接下来的N行中,每行包含两个用空格隔开的整数D和W,用来描述每盏灯的参数,其中0≤D≤1000,0≤W≤1000。

D表示该路灯与村庄开始处的距离(用米为单位来表示),W表示灯泡的功率,即在每秒种该灯泡所消耗的能量数。路灯是按顺序给定的。

输出格式
第一行即唯一的一行应包含一个整数,即消耗能量之和的最小值。注意结果小超过1,000,000,000。


很显然,对于某一段路灯区间 [ i,j ],我们必然要在关完灯后站在 i 或 j 上,否则就会多走路。所以它必然由 [ i+1,j ] 或 [ i,j-1 ] 得来。进一步思考,最后站在 i 或 j 上,对于向更大区间的转移来说是两种不同的情况,所以我们增设一维。

设 f[ i ][ j ][ 0 ] 表示关完 [ i,j ] 后站在左边的 i 上,设 f[ i ][ j ][ 1 ] 表示关完 [ i,j ] 后站在右边的 j 上。
可得状态转移方程:
f[ i ][ j ][ 0 ] = min { f[ i+1 ][ j ][ 0 ] + 从 i+1 走到 i 的时间里未关的灯的能耗,f[ i+1][ j ][ 1 ]+从 j 走到 i 的时间里未关的灯的能耗 }
一开始没想通为什么还要从 [ i+1 ][ j ][ 1 ]转移,我觉得从 j 走到 i 会比另一种情况多走,但实际上最终站在左边或右边是两种完全不同的走法,转移方程中后者完全有可能通过玄妙的走位以很少的时间关完灯站好,所以即使从右边走到 i 比较远,后者也有可能大于前者。

至于某段时间里未关的灯的能耗怎么求呢?我们预处理一个前缀和数组 w[ i ],表示1~i 路灯单位时间的能耗总和,从而求得数组 cost[ i ][ j ],表示除了 [ i,j ] 外的灯单位时间的能耗总和: cost [ i ][ j ]=w[ n ] - w[ j ] + w[ i-1 ];
那么,从 i+1 走到 i 的时间里未关的灯的能耗即为 cost[ i ][ i+1 ] * ( dis[ i+1 ] - dis[ i ] )。

同理可得 f[ i ][ j ][ 1 ] 的转移方程,见 code,不再赘述。

#include<bits/stdc++.h>
using namespace std;

int n,v;
int dis[1010],w[1010];

int f[1010][1010][2];
int cost[1010][1010]; 

inline void read (int &x)
{
    x=0;int f=1;char s=getchar();
    for(;s<'0'||s>'9';s=getchar()) if(s=='-') f=-1;
    for(;s>='0'&&s<='9';s=getchar()) x=(x<<3)+(x<<1)+s-48;
    x*=f;
}

void init()
{
    read(n);read(v);
    memset(dis,0,sizeof(dis));
    memset(w,0,sizeof(w));
    memset(cost,0,sizeof(cost));
    int x;
    for(int i=1;i<=n;++i) 
    {
        read(dis[i]);read(x);
        w[i]=w[i-1]+x;
    }
    for(int i=1;i<=n;++i) 
        for(int j=i;j<=n;++j)  cost[i][j]=cost[j][i]=w[n]-w[j]+w[i-1];
}

void work()
{
    memset(f,10,sizeof(f));
    f[v][v][0]=f[v][v][1]=0;
    for(int k=2;k<=n;++k)
        for(int i=1;i<=n-k+1;++i)
        {
            int j=i+k-1;
            f[i][j][0]=min(f[i+1][j][0]+cost[i+1][j]*(dis[i+1]-dis[i]),f[i+1][j][1]+cost[i+1][j]*(dis[j]-dis[i]));
            f[i][j][1]=min(f[i][j-1][0]+cost[i][j-1]*(dis[j]-dis[i]),f[i][j-1][1]+cost[i][j-1]*(dis[j]-dis[j-1]));
        }
    printf("%d",min(f[1][n][0],f[1][n][1]));
}  

int main()
{
    init();
    work();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值