好题。
思路
首先贪心的考虑,对于两个数 \(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\) 时如下:
观察发现此时仍然可以得到两组互补的数,即:\((2, 5)\)、\((3, 4)\)。此时考虑先把这两组的答案计算上,这样互补的数中的每一位都用上了,所以一定不劣。那剩下的数怎么办呢?
注意到标红部分属于二进制中相同的高位,于是乎把它们去掉,然后便得到了:
于是又得到了一对互补的数。
根据上述手玩的过程,我们归纳出一个方法:
对于一段区间 \([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\) 是怎么来的?
还是上面那组样例:
此时的 \(pos=3\),容易发现互补的匹配是从 \(pos\) 开始对称的,即:
所以我们可以得到在计算完所有完美匹配后的 \(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;
}