二分(长城守卫,LA 3177)

一看到这题,以为是数学,真的不会。

事实上这是算法比赛,数学题不一定要用数学的解法。你看这题,要你找某个尽量小的值来满足一些事。简直就是明示你二分啊。而且满足不满足是连续的,因此用lower_bound。

一直觉得一定是有某个公式算出答案。

其实没有那么多公式化的东西,更重要的是解决问题。如果实在没有头绪,那就自己给自己出几个题目,想尽各种办法先得到答案,也许你就会找到规律,从而找到能用代码解决的方法。而不是一开始就去找一个能用代码解决的方法。你至少应该能想到偶数的解法的,而你却没有去做。

首先每个礼物有无穷多个,唯一的区别就是要不同,不妨把礼物编号,然后第一个人取前r[i]个。

偶数的情况会十分简单,因为奇数编号的人只与偶数编号的人相邻,只要让每一对相邻的人都能相安无事便可,我们让奇数号的人从前面取,让偶数号的人从后面取,待取的礼物一定会被释放,有点像滚动数组的感觉。礼物只要大于相邻和的最大值即可。

然而这并没有什么乱用,因为这个方法无法解决奇数的情况,奇数无法像滚动数组那样,更像是动态规划。因此能解决奇数的方法也一定能解决偶数的问题。

直接算的话该如何决策都不知道,只能枚举。也许只有动态规划能解,但这种环状的东西也不知道该怎么办。

决策似乎和礼物数有关,也就是说在知道礼物数之前很难想到某种分配方法。这时就应该要想到用二分了。能想到二分答案,问题就解决了一半。

接下来是如何判断满不满足。

还是一样,礼物编号,第一个人取前r[i]个。那么最后一个人就不能取前r[i]个,但若是他的前一个人抢了某些他的选择,加上礼物数不够,那就会导致分配失败。

因此最后一个人要尽量取后面的,而他的前一个人要尽量取前面的,推到第二个人,那就是偶数要尽量取前面,奇数要尽量取后面。因此就模拟一下,看下能不能就好了。

模拟的方法很重要,优质的模拟方法能大大减少代码量和时间空间复杂度。

最简单无脑的方法莫过于用一个集合或者数组来模拟了吧。。我估计自己也就只能想到这样的方法了。

但是你不必模拟得过于细节,比如具体到礼物的编号,这十分没有必要,我们之关心礼物的个数。

我们只要知道他“前面”取了几个礼物,“后面”取了几个礼物即可。递推时加加减减就出结果了。

前面,后面是相对而言的。因此你要划清一个界限,之前的叫前面,之后的叫后面。这个界限用第一个人取的数r[i]来划分就十分不错。初始化和得出结论都十分方便。

然后就根据前面一个人的取法来得到自己的取法。

至于二分的范围,适当的范围能避免模拟时不必要的讨论,比如中途礼物数不够了,或者说上界明显过大了之类的。

大白书上的范围很值得学习,卡的很稳。不像我直接就全范围二分导致讨论时出错然后WA。

限定下届是为了能稳稳的推到最后,限定上界是为了减少不必要的计算。

若想稳稳的推到最后,可参考偶数的情况。

上界则是3*max(r[i])。本来2*max(r[i])就应该足够,但是为了避免n=3之类的这种情况,又希望能简单点算出来,那就取3*max(r[i])。其实这无关紧要,但我觉得你至少该想想而不是直接上个无脑大的东西。


代码

#include<bits/stdc++.h>
#define maxn 100010
using namespace std;
typedef long long ll;

ll n;
ll r[maxn];
ll LEFT[2];
ll RIGHT[2];
ll now;

bool ok(ll MAX)
{
    LEFT[now]=r[1];
    RIGHT[now]=0;
    for(ll i=2;i<=n;i++)
    {
        if(i&1)
        {
            RIGHT[now^1]=min(r[i],max(MAX-r[1]-RIGHT[now],0ll));
            if(RIGHT[now^1]<r[i]) LEFT[now^1]=r[i]-RIGHT[now^1];
            else LEFT[now^1]=0;
        }
        else
        {
            LEFT[now^1]=min(r[i],max(r[1]-LEFT[now],0ll));
            if(LEFT[now^1]<r[i]) RIGHT[now^1]=r[i]-LEFT[now^1];
            else RIGHT[now^1]=0;
        }
        now^=1;
    }
    return LEFT[now]==0;
}

int main()
{
    while(scanf("%lld",&n)==1&&n)
    {
        ll x=0;
        ll y=0;
        now=0;
        for(ll i=1;i<=n;i++)
        {
            scanf("%lld",&r[i]);
            y+=r[i];
        }
        if(n==1) printf("%lld\n",r[1]);
        else if(n&1)
        {
            x=r[1]+r[n];;
            for(int i=1;i<n;i++) x=max(x,r[i]+r[i+1]);
            y++;
            while(x<y)
            {
                ll m=x+(y-x)/2;
                if(ok(m)) y=m;
                else x=m+1;
            }
            printf("%lld\n",x);
        }
        else
        {
            ll MAX=r[1]+r[n];
            for(ll i=1;i<n;i++)
                MAX=max(MAX,r[i]+r[i+1]);
            printf("%lld\n",MAX);
        }
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值