两道用贪心算法的题目对比 (卖酒问题Uva 11054和 环形跑道Uva 11093)

Uva 11054 链接

Uva 11054强调的是劳动力(不是酒),劳动力是不能抵消的,都必须加起来(不管是进,还是出),如果是负,就用绝对值。本人理解,这里劳动力相当于系统的内耗。我们可以把一个村庄或多个村庄一起看成一个村庄,但同时必须加上其系统的内耗。

举例:
a[5] = {2,3,-4,-2,1} //sum of a[i]=0
2,3,-4,-2,1所需的劳动力等价于5,-4,-2,1所需的劳动力+2
5,-4,-2,1所需的劳动力等价于1,-2,1所需的劳动力+5
1,-2,1所需的劳动力等价于-1,1所需的劳动力+1
-1,1所需的劳动力等价于0所需的劳动力+1 (绝对值)
所以总共劳动力为2+5+1+1=9。

代码如下(参考紫书答案, 例题8-5) :

long long WineLabor(long long a[], int len) {
     long long last, ans;

     for (int i=0; i<len; i++) {
         ans += abs(last);
         last += a[i];
     }
     return ans;
}

其ans就是所需累计劳动力,若a[5]={2,3,-4,-2,1},ans的值分别为0,2,7,8,9。last 就是sum of a[i],其值为2,5,1,-1,0。
注意:
1) n个村庄的劳动力计算为n-1次。我们算ans只需要算到a[n-2],即倒数第2个元素。因为倒数第2个村庄和倒数第1个村庄之间的劳动力算1次即可。比如说最后2个村庄的a[i]值为[-3,3],可知需要3个劳动力。
2) 此题还有一个环形变种 UVA - 11300 Spreading the Wealth。先记下来,以后再做。

Uva 11093链接

Uva 11093里面,汽车每个加油站先加油x1,然后消耗掉x2,净剩x1-x2。该题里面每个加油站一进一出,只需计算净剩值(注意跟Uva11054不一样),而不是绝对值之和。

注意:
1) 该题有环,可以用i=(i+1)%n,也可以将一个环扩展成两个环,从而像链表一样操作。

方法1:一环变两环。(参考了https://menyifan.com/2015/10/18/uva11093/)

#include <iostream>

using namespace std;

const int maxn=100000+5;
int n, p[maxn], q[maxn];


int travel(int p[], int q[], int n) {
    int finish = 0;
    int ans = -1;
    for (int start=0; start<n; start=finish) {
        finish = start;
        int fuel = 0, sum = 0;
        while ((ans<0) && (fuel>=0)) {
            fuel+=p[finish]-q[finish];
            finish++;
            sum++;
            if ((sum==n) && (fuel>=0))
                ans=start;
        }

        if (ans>=0)
            return ans+1;   //因为下标从0开始算
    }

    return ans;
}

int main()
{
    int T;
    scanf("%d", &T);
    for (int k=1; k<=T; k++) {
        scanf("%d", &n);
        for (int i=0; i<n; i++) {
            scanf("%d", &p[i]);
            p[i+n]=p[i];
        }
        for (int i=0; i<n; i++) {
            scanf("%d", &q[i]);
            q[i+n]=q[i];
        }

        int ans = travel(p,q,n);
        printf("Case %d: ", k);
        if(ans < 0)
            cout<<"Not possible"<<endl;
        else
            cout<<"Possible from station "<<ans<<endl;
    }
    return 0;
}

注意while()的条件。这里必须用fuel>=0,而不能用sum<=n。否则中途fuel<0时while()仍然会继续,从而使得某个不合格的站点混水摸鱼。
例如:
input p[] = {1,3,1,4,2,1,2,3,2}
input q[] = {2,2,1,3,1,3,1,1,1}
当start = 0时,debug如下
start=0 finish=0 fuel=0
start=0 finish=1 fuel=-1 //这里while()仍会继续
start=0 finish=2 fuel=0
start=0 finish=3 fuel=0
start=0 finish=4 fuel=1
start=0 finish=5 fuel=2
start=0 finish=6 fuel=0
start=0 finish=7 fuel=1
start=0 finish=8 fuel=3
find! start=0 finish=9
Case 1: Possible from station 1

方法2:还是用经典的i = (i+1)%n。这也是紫书的解法。注意当输出的ans大于起始站点时说明已经wrap,也即无解。
代码如下:


int travel2(int p[], int q[], int n) {

    int start = 0, finish =0, fuel = 0;
    while(1) {
        //重新开始
        finish = start+1;
        fuel = p[start] - q[start];
        while (finish != start) {   //no wrap,注意这里不可以用finish > start, 否则wrap后自动退出
            if ((fuel < 0))
                break;
            fuel += p[finish] - q[finish];
            finish = (finish + 1) % n;
        }

        if (finish > start)
            start = finish;
        else if (finish < start)
            return -1;
        else //finish == start, 走完一圈,但是还是要看fuel
            if (fuel >= 0)
                return finish + 1; //因为下标从0开始算
            else
                return -1;
    }
}

注意以上两种方法复杂度都是O(n)。因为虽然有两个循环嵌套,但每个站点都只算了一次。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值