好题题解

__liujy 2026-7-23 17:10:58 19 浏览 2 点赞 0 收藏

P16995 【MX-S15-T1】「DLESS-5」Another OR Problem

好题!

看到这个按位或求 max\max 很难不想到按位枚举,但是这个 b=k\sum{b} = k 的限制很烦人,可以考虑能不能知道当 aa 固定时,随着 kk 的增大,答案的大小变化。

fk(a)f_k(a) 表示

$$\max_{\sum{b} = k}\{ \lor_{i = 1}^{n} (a_i + b_i)\} $$

考虑 fk(a)f_k(a)fk+1(a)f_{k+1}(a) 的大小关系。

cic_{i}ai+bia_i + b_i,其中 b=k\sum b = ki=1nci=fk(a)\lor_{i = 1}^{n}c_i = f_k(a)

考虑分讨:

  1. 当二进制下的每一个 cic_i 的末尾都为 11 时,可以选择末尾连续 11 的数量最小的 cic_i 加一,这样,fk+1(a)fk(a)f_{k+1}(a) \ge f_{k}(a)
  2. 当存在二进制下的 cic_i 末尾为 00,可以直接对这个 cic_i 加一,这时,fk+1(a)fk(a)f_{k+1}(a) \ge f_{k}(a)

综上,fk+1(a)fk(a)f_{k+1}(a) \ge f_{k}(a),即数列 {fk(a)}\{f_{k}(a) \} 单调不降。

然后就按位枚举,考虑怎么写 check(x)\operatorname{check}(x)

如果已经存在 cic_i 的第 xx 位为 11,显然满足条件。

否则如果让 cic_i 的第 xx 位为 11,就需要加上 2x(cimod2x)2^x - (c_i \bmod 2^x),然后选的肯定是这个值最小的,然后判一下就做完了。

这样就能获得 4040 分的高分!

考虑正解。

当进行完操作后 kk 不为 00 时,说明还有若干位需要被填,假设 kk 在二进制下的第 jj 位为 11,考虑这一个 2j2^j 加在哪一个 bib_i 上。若有一个 aia_i 满足在二进制下的第 ii 位为 00,那么显然可以加在这个 bib_i 上,否则选择从第 jj 位到最高位连续 11 的个数最小的那一个 ii 加上,这是因为 fk(a)f_k(a) 是单调不降的。

#include<bits/stdc++.h>
const int MAXN = 1e6 + 5;
#define pb push_back
typedef long long LL;

typedef std::pair<int, int> PII;

typedef std::pair<LL, int> PLI;

int n; LL k, a[MAXN], b[MAXN];

LL sum;

inline bool check(int x)
{
	for(int i = 1; i <= n; i++)
		if((a[i] >> x) & 1)
			return 1;
	
	PLI ans = {LLONG_MIN, 0};
	
	for(int i = 1; i <= n; i++)
		ans = std::max(ans, {a[i] % (1ll << x), i});
		
	LL nd = (1ll << x) - ans.first;
//	std::cout << sum << ' ' << nd << ' ' << ans.first << ' ' << (1ll << x) << '\n';
	if(sum + nd <= k)
	{
		sum += nd, a[ans.second] += nd, b[ans.second] += nd;
//		std::cout << x << '\n';
		return 1;
	}
	else return 0;
}

inline void solve()
{
	scanf("%d%lld", &n, &k);
	for(int i = 1; i <= n; i++)
		scanf("%lld", &a[i]);
		
	for(int i = 1; i <= n; i++)
		b[i] = 0;
		
	LL ans = 0; sum = 0;
	
	for(int j = 60; ~j; j--)
		if(check(j))
			ans |= (1ll << j);
			
	k -= sum;
	if(k) for(int j = 60; ~j; j--)
	{
		if(!((k >> j) & 1)) continue;
		
		bool fl = 0;
		
		for(int i = 1; i <= n && !fl; i++)
			if(!((a[i] >> j) & 1))
				a[i] += (1ll << j), b[i] += (1ll << j), fl = 1;
				
		if(fl) continue;
				
		PII ans = {INT_MAX, 0};
		
		for(int i = 1; i <= n; i++)
		{
			LL x = (a[i] >> j);
			
			int cnt = 0;
			while(x)
			{
				if(!(x & 1))
					break;
					
				x >>= 1, cnt++;
			}
			
			ans = std::min(ans, {cnt, i});
		}
		
		a[ans.second] += (1ll << j), b[ans.second] += (1ll << j);
	} 
			
	printf("%lld\n", ans);
	
	for(int i = 1; i <= n; i++)
		printf("%lld%c", b[i], (i == n ? '\n' : ' '));
}

int main()
{
	int c, T;
	scanf("%d%d", &c, &T);
	
	while(T--)
		solve();
		
	return 0;
}

评论

0 条
还没有评论。