POJ 3484 Showstopper 二分

一、题目大意

我们有N个数列,每个数列可以用X,Y,Z三个数字代表,数列的元素为 X,X+Z,X+2*Z,X+K*Z<=Y,题目保证这个N个数列中的每个元素出现的次数都为偶数次(即第1个序列中的某个元素,一定会在其他的数列中出现,并且出现次数是偶数)

但多次偏差之后,N个数列中存在一个元素,在所有数列中出现的次数为奇数次,题目让我们找到这个元素和它出现的次数,如果所有元素都出现了偶数次,那么输出no corruption。

二、思路

对这个元素使用二分即可,left选择-1,right选择最大的Y+1

二分的依据如下

不难看出,当一个数字不属于任何一个数列时
它在所有数列中出现的次数就是0,也是一个偶数

当一个数字属于其中某一个数列时
根据题意,它一定也在其他的数列出现,出现的次数和也会是偶数

如果所有数列中每一个元素出现的次数都是偶数次
那么所有数列中元素的个数之和也一定是偶数

但是如果其中有且仅有一个元素出现了奇数次
那么所有元素的数量之和也一定是奇数

假如我们对元素的右边界进行二分枚举
当mid小于那个出现次数为奇数的元素时
所有数列中小于等于mid的元素个数一定为偶数
当mid大于等于那个出现次数为奇数的元素时
所有数列中小于等于mid的元素个数一定为奇数

因此不断的二分,一个极限limit时
当mid=limit,所有数列小于等于mid的元素数量为偶数
当mid=limit+1,所有数列小于等于mid的元素数量为奇数
那么这个limit+1,就是那个唯一的差错值,也是本题目的答案

原理就是,多个偶数相加也一定时偶数,但只有其中有一个奇数
那么最终的和也一定是一个奇数
所以通过二分,可以不断的接近,迅速找到一群偶数中的唯一奇数


本题目的输入也比较特殊,我在编写输入函数上也花费了一些时间,最终的思路一个字符一个字符的读入,就是对回车符号、空格和数字区分处理,在这里不再赘述

三、代码

#include <iostream>
using namespace std;
typedef long long ll;
int n;
char str[128];
ll x[100007], y[100007], z[100007], maxt;
bool judge(ll mid)
{
    ll count = 0;
    for (int i = 1; i <= n; i++)
    {
        if (mid < x[i])
        {
            continue;
        }
        ll right = min(y[i], mid);
        ll currentCount = (right - x[i]) / z[i];
        currentCount++;
        currentCount = currentCount % 2;
        count += currentCount;
    }
    return count % 2 == 0;
}
void findMax()
{
    maxt = 0;
    for (int i = 1; i <= n; i++)
    {
        maxt = max(maxt, y[i]);
    }
}
ll findCount(ll mid)
{
    ll count = 0;
    for (int i = 1; i <= n; i++)
    {
        if (mid < x[i] || mid > y[i])
        {
            continue;
        }
        if ((mid - x[i]) % z[i] == 0)
        {
            count++;
        }
    }
    return count;
}
void binarySearch()
{
    findMax();
    ll left = -1, right = maxt + 1;
    while (left + 1 < right)
    {
        ll mid = (left + right) / 2;
        if (judge(mid))
        {
            left = mid;
        }
        else
        {
            right = mid;
        }
    }
    if (right == maxt + 1)
    {
        printf("no corruption\n");
    }
    else
    {
        printf("%lld %lld\n", right, findCount(right));
    }
}
bool isNumber(char c)
{
    char number[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
    for (int i = 0; i < 10; i++)
    {
        if (c == number[i])
        {
            return true;
        }
    }
    return false;
}
void resetArray()
{
    // 多处理一位,多一份安全
    for (int i = 0; i <= n + 1; i++)
    {
        x[i] = 0;
        y[i] = 0;
        z[i] = 0;
    }
}
int main()
{
    n = 1;
    char c;
    // 接收到换行符的数量
    int enterCount = 0, mode = 1;
    while (~scanf("%c", &c))
    {
        if (isNumber(c))
        {
            // 始于数字
            enterCount = 0;
            switch (mode)
            {
            case 1:
                x[n] = x[n] * 10 + (c - '0');
                break;
            case 2:
                y[n] = y[n] * 10 + (c - '0');
                break;
            case 3:
                z[n] = z[n] * 10 + (c - '0');
                break;
            default:
                break;
            }
        }
        else if (c == ' ')
        {
            // 如果是空格,代表开始输入下一个数字
            mode++;
        }
        else if (c == '\n')
        {
            enterCount++;
            // 如果是回车,单独的一个回车,那么n自增
            if (enterCount == 1)
            {
                n++;
                mode = 1;
            }
            else if (n > 1)
            {
                // 如果第n行的x为0.那么n取大了
                if (x[n] == 0)
                {
                    n--;
                }
                // 多个回车,并且当前有数据,直接处理
                if (n > 1)
                {
                    binarySearch();
                    resetArray();
                    n = 1;
                }
            }
        }
    }
    // 输入结束,如果n还有值,那么在处理一次
    if (x[n] == 0)
    {
        n--;
    }
    if (n > 1)
    {
        binarySearch();
    }
    return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值