D2. Infinite Sequence (Hard Version)
题解:无限序列(困难版)
问题描述
给定一个正整数 $ n $ 和一个无限二进制序列的前 $ n $ 项,序列满足递归定义:
\[a_m = a_1 \oplus a_2 \oplus \cdots \oplus a_{\lfloor m/2 \rfloor} \quad \text{(对于 $ m > n $)}
\]
其中 \(\oplus\) 表示按位异或。
现要求计算区间 \([l, r]\) 内所有序列元素的和,注意 $ l, r $ 可能非常大(最高达 \(10^{18}\)),而 $ n $ 的大小最多 \(2 \times 10^5\)。
解题思路
1. 序列的自相似性与递归定义
- 序列的递归定义使得 $ a_m \((\) m > n $)可以通过前 \(\lfloor m/2 \rfloor\) 个元素异或得到。
- 这种定义导致序列具有“翻倍扩展”的自相似结构。例如,若 $ x $ 是某个下标,则其子序列 $ x \times 2^j \((\) j \geq 0 $)可以通过不断除以 2 缩放回较小范围进行计算。
2. 预处理部分
- 输入前 $ n $ 项:读取并存储序列的前 $ n $ 项 $ a_1, a_2, \ldots, a_n $。
- 构造前缀异或数组:
定义 \(\text{pre}[i] = a_1 \oplus a_2 \oplus \cdots \oplus a_i\),用于快速查询任意区间的异或结果。 - 序列扩展:
若 $ n $ 为偶数,为了统一后续递归处理,将序列扩展一项,令\[a_{n+1} = \text{pre}[n/2] \]并更新前缀异或数组。
3. 递归计算单个位置 $ a_x $ 的值
设计一个函数 get(x),用于计算任意位置 $ x $ 的值:
- 若 $ x \leq n $,直接返回 $ a_x $。
- 若 $ x > n $,利用递归关系逐步将 $ x $ 归约到 \([1, n]\) 范围:
- 将 $ x $ 不断除以 2,记录归约过程中每次的奇偶性变化。
- 若新下标为偶数且大于 $ n $,则异或上 \(\text{pre}[n]\)。
- 最终通过预处理的 \(\text{pre}\) 数组得到结果。
4. 区间求和的分块处理
由于区间 \([l, r]\) 可能非常大,无法逐项递归计算。因此采用以下方法分块处理:
(1) 边界处理
- 先将 \([l, r]\) 内属于前 $ n $ 项的部分直接加到答案中。
- 对于 $ x > n $ 的部分,分别处理奇偶边界:
- 若 $ r $ 为偶数,先单独处理 $ a_r $。
- 若 $ l $ 为奇数,先单独处理 $ a_l $。
- 剩下的区间保证起点为偶数、终点为奇数,方便后续缩放操作。
(2) 区间缩放
- 将区间 \([l, r]\) 整体除以 2,即缩放为 \([\lceil l/2 \rceil, \lfloor r/2 \rfloor]\)。
- 计算下界时使用向上取整公式:\[L = \max(n+1, \lceil l/2 \rceil) = \max(n+1, (l+1)/2) \]确保缩放后下界不小于 $ n+1 $。
- 上界直接向下取整:\[R = \lfloor r/2 \rfloor \]
(3) 区间内元素个数
- 缩放后的区间 \([L, R]\) 中,元素个数为 $ R - L + 1 $,表示在当前层级下有多少个“缩放后的下标 $ a $”对应的原序号 $ a \times 2^j $ 落在 \([l, r]\) 内。
(4) 分层贡献的求和
- 对于固定倍数因子 $ x = 2^j $,遍历缩放后的下标 $ a $:
- 若 $ a \in [n/2+1, n] $,累加贡献:\[2 \times (t \oplus \text{pre}[a]) \]其中 $ t $ 是调整值,用于修正递归中的奇偶性变化。
- 若 $ a > n $,利用区间内统一的贡献 $ t \oplus \text{pre}[n] $ 和元素个数 $ R - L + 1 $ 快速求和。
- 若 $ a \in [n/2+1, n] $,累加贡献:
(5) 调整值 $ t $ 的含义
- 由公式 $ t = \text{__builtin_ctzll}(x) & \text{pre}[n] $ 计算:
__builtin_ctzll(x)返回 $ x $ 的二进制中最低有效位 \(1\) 出现的位置。- $ t $ 结合 \(\text{pre}[n]\) 的值,校正经过多次除以 2 后各层级的异或状态。
#include<bits/stdc++.h>
#define int long long
#define all(x) x.begin(),x.end()
#define rall(x) x.rbegin(),x.rend()
#define pb push_back
#define pii pair<int,int>
using namespace std;
const int mod=1e9+7;
int gcd(int a,int b){return b?gcd(b,a%b):a;};
int qpw(int a,int b){int ans=1;while(b){if(b&1)ans=ans*a%mod;a=a*a%mod,b>>=1;}return ans;}
int inv(int x){return qpw(x,mod-2);}
constexpr int N=1e5+10;
int dp[N][2];
vector<int>G[N];
void solve() {
int n,l,r;cin>>n>>l>>r;
vector<int>a(n+1),pre(n+1);
for (int i=1;i<=n;i++)cin>>a[i],pre[i]=pre[i-1]^a[i];
if (n%2==0) {
a.pb(pre[n/2]),pre.pb(a[n+1]^pre[n]);
n++;
}
auto get=[&](int x) {
if (x<=n)return a[x];
int res=0,i=x/2;
while (i>n&&i%2==0) {
res^=pre[n];
i/=2;
}
res^=pre[min(i,n)];
return res;
};
int ans=0;
for (int i=l;i<=r&&i<=n;i++) {
ans+=a[i];
}
if (r>n) {
l=max(l,n+1);
if (l<=r&&r%2==0) {//将其规整化为偶为l奇数为r的状态
ans+=get(r);
r--;
}
if (l<=r&&l%2==1) {
ans+=get(l);
l++;
}
if (l<=r) {
l/=2,r/=2;//将其规整化除二 因为类似于 4 5 除二向下取整都为2 所有需要规整
for (int x=1;x*(n/2+1)<=r;x*=2) {//每次填补半边 其扩展长度为原来的一倍
int t=__builtin_ctzll(x)&pre[n];//实际含义为t=(pre[n])^log2(x)
for (int a=n/2+1;a<=n&&a*x<=r;a++) {
if (a*x>=l) {
ans+=2*(t^pre[a]);//即为部分数除二之后一直存在偶数状态 且除二次数为log2(x)时 最后残留部分位于该区间内
}
}
//接下来处理奇数部分
//对于这些能被缩放比例为x除完之后为奇数且可以直接转化为pre[n]的部分
int R=r/x;//对其进行向下取整 确保区间内的每个缩放之后可以达成的个数
int L=max((l-1+x)/x,n+1);//
if (L <= R) {
ans += 2 * ((R - L + 1) / 2 + (L % 2) * (R % 2)) * (t ^ pre[n]);
}
}
}
}
cout<<ans<<'\n';
}
signed main(){
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
int _=1;
cin>>_;
while(_--)solve();
}

浙公网安备 33010602011771号