紫书第九章-----动态规划初步(例题9-6 Lighting System Design UVA - 11400 )

Lighting System Design UVA - 11400

【题目大意】
有n种功率不同的灯,分别给出每种灯的功率、所需电源费用(所有该功率的灯只需要这一个电源)、每个灯花费钱数和该种灯所需个数。这是一个工程的要求。可以用大功率灯代替小功率灯。求最少花费多少钱。
【分析】

  • **要么不换,要么全换!**有个事实是,如果用小功率的灯换成大功率的灯,那么一定是所有的小功率的灯A换成大功率的灯B,不可能是A的一部分替换成B,因为可以分两类讨论!第一类,A的价格高于B的价格,如果只替换一部分,那么A中被替换的一部分减少了金钱开销,那为什么不全部替换成B呢?这样不仅仅所有A减少了开销,而且还省去了A的电源费;第二类,A的价格低于B,如果只替换一部分,那么A中被替换的一部分增加了金钱开销,每多替换一个就多增加一份开销,A的电源费依然如是,这样仅仅增加了开销,这倒不如一个都不替换,或者全部替换,以增加开销为代价来换取去掉A所需的电源费。综上可知,要么替换全部,要么全不替换。
  • 大功率可替换掉小功率,反之不可
  • 按照功率从小到大对灯的种类进行排序,由于小功率的灯可以用大功率的灯代替,而大功率的灯不能用呢小功率的灯代替,那么我们只需要按照功率从小到大的顺序枚举灯,逐一查看小于某功率的灯的所有灯是否能用这个灯代替即可(这句话错误)。如果分别用x1,y1,n1表示小功率灯的电源费用、单价、个数,x2,y2,n2表示大功率灯的电源费用、单价、个数,那么小功率灯可以被大功率灯代替的条件是:
x2+y2*n2+y2*n1<x1+y1*n1+x2+y2*n2,即x1+n1*(y1-y2)>0

上面的逐一查看小于该功率的所有灯,这个思想是错误的,因为这无法达到全局最优,仅仅是局部最优,明显错误,上述错误思想对应的代码如下:

【错误代码】

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int maxn=1005;

int n;

typedef struct{
    int power;
    int source_cost;
    int lamp_cost;
    int cnt;
}node;

node lamp[maxn];
bool vis[maxn];

bool cmp(node node1,node node2){
    if(node1.power<node2.power) return true;
    return false;
}

int main()
{
    while(cin>>n && n){
        memset(vis,false,sizeof(vis));
        for(int i=1;i<=n;i++){
            cin>>lamp[i].power>>lamp[i].source_cost>>lamp[i].lamp_cost>>lamp[i].cnt;
        }
        sort(lamp+1,lamp+n+1,cmp);
        int sum=0;
        for(int i=2;i<=n;i++){
            for(int j=1;j<i;j++){
                if(!vis[j]){
                    if(lamp[i].source_cost+lamp[i].cnt*(lamp[i].lamp_cost-lamp[j].lamp_cost)>0){
                        lamp[i].cnt+=lamp[j].cnt;
                        vis[j]=true;
                    }
                }
            }
        }
        for(int i=1;i<=n;i++){
            if(!vis[i]){
                sum+=(lamp[i].source_cost+(lamp[i].cnt)*(lamp[i].lamp_cost));
            }
        }
        cout<<sum<<endl;
    }
    return 0;
}

【正确解法】

参考《算法竞赛入门经典》(第2版)的思路,进行动态规划!不过该书没有给出状态转移方程的解释,给出的简短说明也是逻辑不清的。网上代码一片,尽是如出一辙,但均无正确完整解释。笔者经过五六个小时的思考,特此展开论证,给出正确的易于理解的状态转移及相应解释。

首先,num[i]表示前i种灯泡的数量
状态:dp[i]表示前i种灯泡的最小开销

dp[j]表示前j种灯泡的最小开销,那么怎么从dp[j]到dp[j+1]进行状态的递推呢?现在我们这么想,dp[j]表示a[1],a[2],……,a[j](按功率从小到大排序)已经按照最优策略进行了代替,假设代替后的序列用b[1],b[2],……,b[k](b[k]必定等于a[j],因为a[j]类型的灯必须得有,并且由于功率最大,不可能被代替)表示,在1-j范围内,已经达到了费用最小,那么也可断定b[1],b[2],……,b[k]之间不可彼此替代(这意味着b序列按照耗费依次增大排序),要想更新到d[j+1],必定拿b[1],b[2],……,b[k]中的部分或全部被a[j+1]替换掉。由于b[1],b[2],……,b[k]是按照耗费依次增大进行排序的,那么如果b[1]可以被a[j+1]替换的话,b[2],……,b[k]也都一定能被a[j+1]替换,也可能b[1]不能被a[j+1]替换,而后面的有可能存在可以被a[j+1]替换的,也就是说,如果存在1<=flag<=k,有b[flag]可以被a[j+1]替换,那么b[flag]~b[k]必定要被a[j+1]替换。那我们就枚举所有情况(笔者在下面枚举每种情况的同时给出了耗费计算公式。):


(1)
b[1]~b[k]被a[j+1]替换,相当于前0种灯已达到费用最小dp[0],1~k种灯都被第j+1种灯替换;
dp[0]+(num[j+1]-num[0])*lamp[j+1].lamp_cost+lamp[j+1].source_cost
(2)
b[2]~b[k]被a[j+1]替换,相当于前1种灯已达到费用最小dp[1],2~k种灯都被第j+1种灯替换;
dp[1]+(num[j+1]-num[1])*lamp[j+1].lamp_cost+lamp[j+1].source_cost
(3)
b[3]~b[k]被a[j+1]替换,相当于前2种灯已达到费用最小dp[2],3~k种灯都被第j+1种灯替换;
dp[2]+(num[j+1]-num[2])*lamp[j+1].lamp_cost+lamp[j+1].source_cost
 ……

(k)
b[k](即b[j])被a[j+1]替换,相当于前j-1种灯已达到费用最小dp[j-1],第j种灯都被第j+1种灯替换;
dp[k-1]+(num[j+1]-num[k-1])*lamp[j+1].lamp_cost+lamp[j+1].source_cost

枚举了从dp[j]到dp[j+1]的所有情况,求出每种情况的耗费,取最小值就是最终的dp[j+1],这样我们就实现了从dp[j]到dp[j+1]的状态转移。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int maxn=1100;

int n;
int num[maxn];
int dp[maxn];

typedef struct{
    int power;
    int source_cost;
    int lamp_cost;
    int cnt;
}node;

node lamp[maxn];

bool cmp(node node1,node node2){
    if(node1.power<node2.power) return true;
    return false;
}

int main()
{
    while(cin>>n && n){
        for(int i=1;i<=n;i++){
            cin>>lamp[i].power>>lamp[i].source_cost>>lamp[i].lamp_cost>>lamp[i].cnt;
        }
        sort(lamp+1,lamp+n+1,cmp);
        int min_cost=0;
        dp[0]=0;
        num[0]=0;
        num[1]=lamp[1].cnt;
        for(int i=2;i<=n;i++){
            num[i]=num[i-1]+lamp[i].cnt;
        }
        for(int j=0;j<=n-1;j++){
            min_cost=dp[0]+(num[j+1]-num[0])*lamp[j+1].lamp_cost+lamp[j+1].source_cost;
            //求出dp[j]到dp[j+1]状态转移时候的最小值给dp[j+1]
            //已搞好dp[k],其他替换成lamp[j+1],枚举所有情况,求出最小值
            for(int k=1;k<=j;k++){
                min_cost=min(min_cost,dp[k]+(num[j+1]-num[k])*lamp[j+1].lamp_cost+lamp[j+1].source_cost);
            }
            dp[j+1]=min_cost;
        }
        cout<<dp[n]<<endl;
    }
    return 0;
}

另外,Lighting System Design LightOJ - 1295题目和上面题目是一道题,只需按照题目要求的格式输入输出更改下上述代码即可通过。

完成此题,深感思考深入,方悟其道也……

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值