poj-2586

/*
题意:
对于每一个月来说,如果盈利则盈利S,如果亏空则亏d。每五个月进行一次统计,共统计八次(1-5月一次,2-6月一次.......)
统计的结果是这八次都是亏空。问题:判断全年是否能盈利,如果能则求出最大的盈利。如果不能盈利则输出Deficit

MS公司每月要么盈余,要么亏损,且一年中每个月的盈余是一样的,亏损也是一样的。
财务统计是每五个月统计一次收入总额,即1-5,2-6……8-12,共有8次统计结果,并且这八次结果公司都亏损的。
给出surplus和deficit,求出全年最大的盈余数。
*/

#include <stdio.h>


int getMaxSurplus(int surplus, int deficit) {
    if (surplus >= 4*deficit) {
        return -1;
    }
    int maxSurplusMonthNumIn5Months = 5*deficit/(surplus + deficit);
    // if ( (maxSurplusMonthNumIn5Months*surplus - (5 - maxSurplusMonthNumIn5Months) * deficit)  >= 0) {
    //     return -1;
    // }
    deficit = -deficit;
    int monthStatus[12];
    int surplusSum = 0;
    int amountIn5Moths = 0;
    int FiveMonthBegin = 0;
    for (int i = 0; i < 5; i++) {
        if (maxSurplusMonthNumIn5Months > 0) {
            maxSurplusMonthNumIn5Months--;
            monthStatus[i] = surplus;
            surplusSum += surplus;
            amountIn5Moths += surplus;
        } else {
            monthStatus[i] = deficit;
            surplusSum += deficit;
            amountIn5Moths += deficit;
        }
    }

    for (int i = 5; i < 12; i++) {
        amountIn5Moths -= monthStatus[FiveMonthBegin];
        FiveMonthBegin++;
        if (amountIn5Moths + surplus < 0) {
            monthStatus[i] = surplus;
            surplusSum += surplus;
            amountIn5Moths += surplus;
        } else {
            monthStatus[i] = deficit;
            surplusSum += deficit;
            amountIn5Moths += deficit;
        }
    }

    // printf("=====================\n");
    // for (int i = 0; i <12; i++) {
    //     printf("%d ", monthStatus[i]);
    // }
    // printf("\n=====================\n");

    if (surplusSum < 0) {
        return -1;
    } else {
        return surplusSum;
    }

}

int main() {
    int surplus, deficit;
    while(scanf("%d %d", &surplus, &deficit) != EOF) {
        int annualSurplus = getMaxSurplus(surplus, deficit);
        if (annualSurplus == -1) {
            printf("Deficit\n");
        } else {
            printf("%d\n", annualSurplus);
        }
    }

}


题写的很晦涩....

本题因为枚举空间小,所以即使用最笨的穷举法, 都能AC。

但是最好的做法还是贪心法, 只不过证明贪心选择是OK的需要想想:

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

根据经验,贪心选择往往都在极端处选择。我们看下1-5月里,亏损的月数肯定应尽量往后选,证明如下(虽然很明显,但证明有利于使思维更清晰)

 

假设输入s=7d=5,那么前5个月至少亏损3个月。我们先证明3,4,5月必定被选中。优解中第5个月不选,那随便把前面某个亏损月和第五个月交换,不破换连续5月亏损的条件,所以得到另一个最优解。同理再选第四个,第三个。我们再证明只有3,4,5月被选中,否则得不到最优。因为假设2月被选中。2月包含在1-5,2-6中,对这两组而言,2必定已经是多余的了(1-5 2-6 中已经有了345可以保证区间<0, 而标记2对后面的区间没有任何影响,在345被标记的前提下),因为包含3,4,5就可以使条件满足;而对3-6,4-7...而言,2是否选取对他们没有影响。因此如果最优解包含2,那么去掉2必定是个更优解。得证。所以选3,4,5必定是个贪心选择。

 

处理完1-5后,剩下的问题就好办了呵呵。

 

其实这题的子问题结构倒不是很清晰了,可以认为分叉出现在1-5月的选择中,接下来都是一条直线了(这个问题比较独特的一点,因为各种约束条件, 导致其实贪心的选择空间在1~5就已经定下了全局)(1-5月选定后,6月是s还是d由2-6必亏损这一条件已经确定了,以此类推)。假设最少有3个月亏损,那么1-5月的选择共有c(5,3)+c(5,4)+c(5,5)种情况,所以是一个16叉树,除了根节点外其他节点不再分叉。贪心选择选取其中一根树枝,因为之后不分叉,所以总共只做了一次贪心选择。

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

还有做法更进一步,利用上面的准则,直接推导出了总共的5种可能情况,然后根据 s 和 d来进行选择:

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

       1、若SSSSD亏空,那么全年可能最大盈利情况为: SSSSDSSSSDSS
       2、若SSSDD亏空,那么全年可能最大盈利情况为:SSSDDSSSDDSS
       3、若SSDDD亏空,那么全年可能最大盈利情况为: SSDDDSSDDDSS
       4、若SDDDD亏空,那么全年可能最大盈利情况为: SDDDDSDDDDSD
       5、若DDDDD亏空,那么全年可能最大盈利情况为: DDDDDDDDDDDD

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

这种每个子问题不是相互独立的问题, 第一感觉就应该是动态规划, 不过一般来说, 想出了动态规划, 那么就应该尝试一下贪心, 如果成功, 自底而上 和 自上而底的效率是不一样的。

贪心法真是靠直觉和经验的, 连算法导论上都承认, 贪心每一个固定的模式。

最后还要在scanf上加上 !=EOF, 否则 output limit exceeds.

一行debug的log没消, WA了好几次。

发现有时候基础的数学运算都生疏了.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值