Luogu P6025 设
$f(n)$ 是在$[1, n]$ 建线段树后节点最大下标。求:$$f(i)\oplus f(i+1)\oplus\cdots\oplus f(j)$$ $\oplus$ 是异或。$1\le i\le j\le10^{15}$. 建树细节见下面代码:using ll = long long; void init(ll u, ll ul, ll ur) { if (ul == ur) return; ll um = (ul + ur) / 2; init(u*2, ul, um); init(u*2+1, um+1, ur); } // f(1, 1, n);
观察实现细节:
- 下标从 1 开始。
- 左节点
$2u$ ,右节点$2u+1$ 。 - 如果长度
$n$ 是偶数,区间对半分;如果是奇数,左边比右边多 1。
由于我们只考虑下标,上面的代码可以这样写减少参数。函数意思是:以
ll f1(ll n, ll u) {
if (n == 1) return u;
if (n&1) return std::max(
f1(n/2+1, u*2), f1(n/2, u*2+1)
);
return f(n/2, u*2+1)
} // f(n, 1)
时间瓶颈在于当
判断
(n & n-2) == 1 && n != 1
我们实际上是想判定
$n$ 二进制表示是这种形式$$1\underbrace{00\cdots0}_{t-1(\ge0)\ 个\ 0}1$$ 可以验证存在$t$ 的$n$ 一定满足这个表达式。按照位与,$n$ 的低位一定是 1。减 2 刚好会让第二位到第$w$ 位都取反,其中$w$ 是第 2 位起往高位第一个 1。这$(w-1)$ 位一定会变化。从结果看,更高位一定是 0,所以满足上面语句的$n$ 的二进制表示一定是上面那样。
现在我们可以在
ll f(ll n, ll u) {
if (n == 1) return u;
if ((n & (n - 2)) == 1)
return f(n / 2 + 1, u * 2);
return f(n / 2, u * 2 + 1);
}
还能进一步优化。上面这种特殊情况可以直接写出答案:
$$f(n)=f(2^t+1)=2^{t+1}+1=2n-1$$ 所以选左子树时的答案可以常数求出来。实现时注意一个细节,在f(n, u)
的结构里,根节点不一定是 1。这不能优化最坏时间,这种代码我就不写了。
接着考虑区间异或。考虑转换成
通过打表可以验证这一结论。
实现上,设
if (n <= 3) return v[n];
让
ll k = 1;
for (ll p = 8; p <= n; p <<= 1) k++;
处理连续的区间
ll a = v[2]; // 注意第一个区间 [3, 4] 前面的边界
for (ll p = 4, i = 1; i <= k; i++, p<<=1)
a ^= ((p + 1) ^ (2 * p - 1));
对于多出来的情况,设最后一个区间右端点是
// 不要把 1ll 写成 1,给 int 左移不会扩充成 long long
ll q = 1ll << (k + 1);
如果有多的($n>q$),先把下一个区间左端点处理了:
if (n > q) a ^= 2 * q + 1;
在相同的两个数对(偶,奇)里,如果
if (n > q + 1 && !(n & 1)) a ^= f(n, 1);
这道题就做完了。
#include <iostream>
using ll = long long;
ll v[]{0, 1, 3, 5};
ll f(ll n, ll u) {
if (n == 1) return u;
if ((n & (n - 2)) == 1)
return f(n / 2 + 1, u * 2);
return f(n / 2, u * 2 + 1);
}
ll g(ll n) {
if (n <= 3) return v[n];
ll a = v[2], k = 1;
for (ll p = 8; p <= n; p <<= 1) k++;
for (ll p = 4, i = 1; i <= k; i++, p<<=1)
a ^= ((p + 1) ^ (2 * p - 1));
ll q = 1ll << (k + 1);
if (n > q) a ^= 2 * q + 1;
if (n > q + 1 && !(n & 1))
a ^= f(n, 1);
return a;
}
int main() {
v[2] ^= v[1], v[3] ^= v[2];
ll lf, rg; std::cin >> lf >> rg;
std::cout << (g(rg) ^ g(lf - 1)) << '\n';
}