2026CSP-S模拟17 前缀进位

题目描述

给定一个长度为 nn 的非负整数序列 \(a_1\) ,\(a_2\),……,\(a_n\),以及操作次数上限 K。

一次操作需要选择一个整数 \(r(1<=r<=n)\),并把前 \(r\) 个数各增加 1。

你可以进行 0 到 K 次操作后,求下式的最大可能值:

\(a_1\)&\(a_2\)&……&\(a_n\)

输入格式

第一行两个整数 n,K。

第二行 nn 个整数 \(a_1,a_2,\ldots,a_n\)

输出格式

一个整数,答案最大值。

样例

输入:

3 5
1 4 2

输出:

5

数据范围:

$ 1≤ n ≤ 10^6 , 0≤ a_i < 2^{60}, 0≤k<2^{60}$

思路

转换原始式子

设\(c_i\) 为第 i 位加了多少,那么就有:
$$ k ≥ c_1 ≥ c_2 ≥ …… ≥ c_n$$
可以算出操作次数和为\(c_1\)
那么问题可以转换为:
求一个序列c,使得\(c_i \& a_i\)最大,设该值为\(b_i\)。

确定答案

贪心地考虑,肯定是越高位为1越好。
由 $ 0 ≤ a_i < 2^{60}$ 可得,答案最大不会超过60位bit,所以只需要从高位向低位判断该位能不能为1.

如何判断

设当前枚举到的答案为ans,从 \(a_n\) 向前枚举,当前 \(b_i\) 的最小值为 \(a_i + c_{i+1}\)。
在此基础上,找到最高的 \(b_i\) 为0,但是ans为1的 二进制位 ,将该位由1变为0,其余低位全部取0。将加上的数 -> \(c_i\)。最后判断 \(c_i\) 是否 ≤ k。

点击查看代码
#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N=1e6+100;
int n,k,a[N],m;
signed main(){
	freopen("carry.in" , "r" , stdin);
	freopen("carry.out" , "w" , stdout);
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	} 
	for(int i=60;i>=0;i--){
		int s=(1ll<<i),an=m+s,c=0;
		bool flag=0;
		for(int j=n;j>=1;j--){
			int v=c+a[j];
			int f=(~v)&an;
			if(!f) continue;
			int p=(1ll<<(64-__builtin_clzll(f)))-1;
			c+=(p&an)-(p&v);
			if(c>k){
				flag=1;
				break;
			}
		}
		if(!flag){
			m=an;
		}
	}
	cout<<m;
	return 0;
}

另:关于__builtin一系列函数

一些碎碎念:
遇到一个式子不要着急对原式考虑,可以变换它的适当样子,试着简化原式。
多思考位运算,它会给你想不到的方法

posted on 2026-09-28 17:06  FAYEEEEEEE  阅读(6)  评论(0)    收藏  举报

导航