题目链接:https://ac.nowcoder.com/acm/problem/211996

到主站看:https://blog.csdn.net/weixin_43346722/article/details/109689563

题目

众所周知,一场比赛需要 道签到题。

你得到了一个长度为 ,公差为 的等差数列

给定两个常数 ,你可以进行一次如下操作:选择两个数 和一个非负整数 ,满足 。则本次操作为对于 ,令 ,同时其代价为 (其中 表示按位异或操作)。

输出最小的代价使得操作恰好一次后有

输入

输入共一行,共五个整数 ,含义如题目所述

输出

输出共一行,表示最小代价。

样例输入1

5 3 8 8 12

样例输出1

64

样例输入2

10 9 8 30 234

样例输出2

56

样例输入3

5 3 2 15 30

样例输出3

18

数据范围

,保证 在 long long 范围内,且

思路

这道题是一道贪心。

我们来看式子:

先看左边,其实就是求区间的长度,这个我们可以直接枚举。

然后看右边,对于每一个区间长度,我们可以如果要让它发挥更大的作用,一定是摆在最右边的。(因为最右边的值最大)
那我们要让变了之后的序列总和小于等于 ,那 必然就会有一个取值范围:最小是 ,那最大呢?

那就是
表示序列前面 项的和, 则是区间长度)

那我们知道了范围,但是如果一个一个枚举,就又会超时,那咋整呢?

我们可以贪心得出一个结论:要取的数要尽量在二进制高位上跟 一样。
为什么呢?

我们先设 的第 位是 。那如果 这一位选了 ,对于异或就少个 ,然后后面的 又少个一个 ,那就少了

接着我们设 的第 位是 。那如果 的这一位选了 ,那异或的部分多了个 ,然后后面的 少个一个 ,就刚好抵消,值不变。
那我们要让 的值不超过范围,又要让费用小,那还不如不选。

那我们就从高位枚举,如果 的这一位是 然后 的这一位变成 之后小于等于范围,就把这一位变成

那我们就可以得出对于每一个区间长度最好的 了。

然后把每一个区间长度的出来的费用取最小值,就是答案了。

代码

#include<cstdio>
#include<iostream>

using namespace std;

long long n, d, size, a1, k, y, z, ans, a[100001], sum[100001], realz;

int main() {
    ans = 1e18;

    scanf("%lld %lld %lld %lld %lld", &n, &a1, &d, &k, &y);

    sum[1] = a[1] = a1;
    for (int i = 2; i <= n; i++) a[i] = a[i - 1] + d, sum[i] = sum[i - 1] + a[i];
    //先算出每一个数和前缀和

    while (y < sum[n - size]) size++;
    //要使总和小于等于 y 至少要让区间长度是多少

    for (size; size <= n; size++) {//枚举区间长度
        z = (y - sum[n - size]) / size;

        realz = 0;//贪心选异或值最小的
        for (int i = 60; i >= 0; i--)
            if (((k >> i) & 1) && realz + (1 << i) <= z)
                realz += 1 << i;

        ans = min(ans, size * ((realz ^ k) + k - realz));
    }

    printf("%lld", ans);

    return 0;
}