题解:CF2146D2 Max Sum OR (Hard Version)

好题。

思路

首先贪心的考虑,对于两个数 \(x\)\(y\),必然是当 \(x\)\(y\) 后二进制里全为 \(1\) 是最优的,在下文中我们称其为互补。知道这个结论Easy Version就可以直接从 \(r\) 开始往 \(0\) 枚举,对于每个数都取互补的数即可。

void solve() {
	int l, r;
	ll res = 0;
	cin >> l >> r;
	vector<int> ans(r + 1);
	vector<bool> flag(r + 1);
	for (int i = r; i >= l; i--) {
		if (flag[i]) continue;
		int cnt = 0;
		for (int j = 0; j <= log2(i); j++) {
			if (!((1 << j) & i)) {
				cnt += (1 << j);
			}
		}
		res += (cnt | i);
		ans[i] = cnt;
		ans[cnt] = i;
		flag[i] = flag[cnt] = true;
	}
	cout << res * 2 << "\n";
	for (int i = 0; i <= r; i++) {
		cout << ans[i] << " \n"[i == r];
	}
}

回到本题,因为 \(l\ge 0\),所以上面的贪心方法需要改进。容易得到,二进制中的相同的高位都是可以去掉的,所以我们只需要考虑剩下的位即可。考虑手玩几组数据,当 \(l=2\)\(r=7\) 时如下:

\[010_2\\ 011_2\\ 100_2\\ 101_2\\ 110_2\\ 111_2 \]

观察发现此时仍然可以得到两组互补的数,即:\((2, 5)\)\((3, 4)\)。此时考虑先把这两组的答案计算上,这样互补的数中的每一位都用上了,所以一定不劣。那剩下的数怎么办呢?

\[\textcolor{red}{11}0_2\\ \textcolor{red}{11}1_2\\ \]

注意到标红部分属于二进制中相同的高位,于是乎把它们去掉,然后便得到了:

\[0_2\\ 1_2\\ \]

于是又得到了一对互补的数。

根据上述手玩的过程,我们归纳出一个方法:

对于一段区间 \([l, r]\),在去除二进制中相同的高位后,必然可以被划分成两个区间 \([l, pos]\)\((pos, r]\),其中第一个区间中的数在二进制表示下的最高位为 \(0\),第二个区间中的数在二进制表示下的最高位为 \(1\)。此时,不妨令 \(pos - l + 1 \ge r - pos\)。在这种情况下,\((pos, r]\) 这段区间中的数一定可以与第一个区间中进行互补匹配,于是将匹配的数计算进答案,然后令 \(r\leftarrow 2\times pos - r\),将区间右边界缩小。若 \(pos - l + 1 < r - pos\),则令 \(l\leftarrow 2\times pos - l + 2\)

\(r\leftarrow 2\times pos - r\)\(l\leftarrow 2\times pos - l + 2\) 是怎么来的?

还是上面那组样例:

\[010_2\\ 011_2\\ 100_2\\ 101_2\\ 110_2\\ 111_2 \]

此时的 \(pos=3\),容易发现互补的匹配是从 \(pos\) 开始对称的,即:

\[(3, 4)\\ (2, 5)\\ \]

所以我们可以得到在计算完所有完美匹配后的 \(l\)\(r\) 应该缩小到哪里。此时就有:

  • \(pos - l + 1 \ge r - pos\),则 \(r\leftarrow pos-(r-pos)=2\times pos-r\)
  • \(pos - l + 1 < r - pos\),则 \(l\leftarrow pos + pos - l + 1 + 1 = 2\times pos - l + 2\)

最后的问题便不断的缩小,终止边界为 \(l\ge r\)注意,若 \(l=r\) 则还要计算一个数或上它本身的情况。

在将相同的高位删除时需要带一个 \(\log\),所以总时间复杂度为:\(O(n\log V)\)\(V\) 为取值上限。

代码

部分地方的式子和文中有一些差别,但不影响理解(AC 记录)。

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define i128 __int128
#define inf (1ll << 62)
#define PII pair<int, int>
#define pb push_back
#define fi first
#define se second
using namespace std;

void solve() {
	ll l, r, d = 0, res = 0;
	cin >> l >> r;
	ll x = r, y = l;
	vector<int> ans(r - l + 1);
	while (r > l) {
		// cerr << l << " " << r << "\n";
		ll l1 = l + d, r1 = r + d;
		int num = 0;
		for (int i = 30; i >= 0; i--) {
			int ok = -1;
			bool vis = false;
			for (int j = l; j <= r; j++) {
				if (ok == -1) {
					ok = ((1 << i) & j);
				} else if (ok != ((1 << i) & j)) {
					vis = true;
					for (int k = l; k <= r; k++) {
						if ((1 << i) & k) num++;
					}
					break;
				}
			}
			if (vis) break;
			d += ok;
		}
		l = l1 - d;
		r = r1 - d;
		if (num >= (r - l) / 2 + 1) {
			int pos = r - num, pos1 = l + (pos - l) * 2 + 1;
			res += (pos1 - l + 1) * d;
			for (int i = l, j = pos1; i <= j; i++, j--) {
				ans[i + d - y] = j + d;
				ans[j + d - y] = i + d;
				res += (i | j);
				if (i != j) res += (i | j);
			}
			l = pos1 + 1;
		} else  {
			int pos = r - num + 1, pos1 = r - (r - pos) * 2 - 1;
			res += (r - pos1 + 1) * d;
			for (int i = r, j = pos1; i >= j; i--, j++) {
				ans[i + d - y] = j + d;
				ans[j + d - y] = i + d;
				res += (i | j);
				if (i != j) res += (i | j);
			}
			r = pos1 - 1;
		}
	}
	if (r == l) {
		res += r + d;
		ans[r + d - y] = r + d;
	}
	cout << res << "\n";
	for (int i = 0; i <= x - y; i++) {
		cout << ans[i] << " \n"[i == x - y];
	}
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}
posted @ 2025-09-29 13:00  Tomwsc  阅读(13)  评论(0)    收藏  举报