<think>在莫队算法中,我们维护的区间是前缀和数组的区间,即从 `lp` 到 `rp` 的位置(注意这些位置对应的是原数组的前缀和下标,即0~n)。我们要求的是满足 `pre[i] ^ pre[j] = k` 且 `i < j` 的配对数量(其中 `i` 和 `j` 都在当前区间内)。
但是,在移动指针时,我们考虑的是当前区间内所有满足条件的配对。在移动左指针(`lp`)时,我们移除位置 `lp`(注意这个位置是前缀和数组中的一个位置,对应原数组的前缀和下标为 `lp`)。当我们移除 `lp` 时,我们需要减去所有以 `lp` 为左端(即 `pre[i]` 中的 `i` 为 `lp`)的配对。也就是说,我们需要减去满足 `pre[lp] ^ pre[j] = k` 的 `j` 的数量(其中 `j` 在区间内且 `j>lp`)。这个数量就是 `cnt[pre[lp]^k]`。注意,在移除之前,`cnt[pre[lp]]` 还没有被移除,所以 `cnt[pre[lp]^k]` 表示的就是与 `pre[lp]` 配对的 `pre[j]` 的数量。
同理,当加入一个位置时,我们也是先计算加入后能新增多少配对(即当前区间内有多少个 `pre[j]` 满足 `pre[j] = pre[new] ^ k`),然后再将这个位置的值加入计数。
因此,在修正代码中,我们调整了指针移动的顺序:
1. 当左指针收缩(`lp` 右移)时:
- 先移除 `lp` 位置:将 `cnt[pre[lp]]` 减1(注意,此时 `lp` 还在区间内,所以先减1)
- 然后减去配对:`s -= cnt[pre[lp]^k]` (注意,此时 `pre[lp]` 已经被移除,所以这里 `cnt[pre[lp]^k]` 表示的是其他位置与 `pre[lp]` 配对的个数,但注意,在移除 `pre[lp]` 后,这些配对就不存在了,所以减去)
- 但实际上,这个顺序应该是:先减去配对,再移除位置?因为如果先移除位置,那么 `cnt[pre[lp]^k]` 就不包括 `pre[lp]` 自己了?但是,我们移除的是位置 `lp`,而配对要求的是 `j>lp`,所以 `pre[lp]` 自己不会和自己配对(因为同一个位置不能作为两个端点?)。然而,这里有一个细节:配对要求两个不同的位置,所以不会出现自己和自己配对(除非k=0,但自己和自己配对也是不允许的,因为区间需要两个不同的端点)。
然而,更标准的做法是:在移除一个位置时,先将其从计数中移除,然后更新配对数量。但这样更新配对数量时,需要减去的是移除前该位置能配对的个数(即移除前 `cnt[pre[lp]^k]` 的值)。但因为我们刚刚已经移除了自己,所以此时 `cnt[pre[lp]^k]` 已经少了这个位置(如果之前有的话)?不对,因为配对是和其他位置配对,移除自己后,其他位置还在,所以 `cnt[pre[lp]^k]` 还是其他位置中等于 `pre[lp]^k` 的数量。但是,这个数量恰好就是我们需要减去的:因为移除 `lp` 后,所有以 `lp` 为左端(或右端,具体看定义)的配对都不存在了。
然而,在莫队算法中,我们通常这样处理:
设当前区间为 [L, R],我们维护的是区间内所有位置的前缀和值的计数数组 `cnt`,以及配对总数 `s`。
当我们要将左指针L向右移动(即收缩左边界)时,我们移除位置L(注意这个位置是前缀和数组中的位置,对应原数组的前缀和下标L):
- 首先,我们减少配对:这个位置L作为配对中的左端(即i=L)时,配对数量为 `cnt[pre[L]^k]`(因为配对要求 `pre[i]^pre[j]=k`,即 `pre[j]=pre[i]^k`,所以j的数量就是 `cnt[pre[L]^k]`)。
- 然后,我们将 `cnt[pre[L]]` 减1,因为位置L被移除。
但是,在修正代码中,我们却先移除再减配对?这样就会导致减去的配对数量不正确(因为移除后,`cnt[pre[L]^k]` 已经少了一个可能的位置L(如果 `pre[L]^k` 等于 `pre[L]` 的话,即k=0,那么移除自己后,自己就不能和自己配对了)?所以实际上,我们应该先计算配对数量,再移除。
因此,修正代码中左指针收缩的部分应该改为:
```cpp
while (lp < l) {
// 先减去以lp为左端(即i=lp)的配对数量
s -= cnt[pre[lp] ^ k];
cnt[pre[lp]]--;
lp++;
}
```
同理,左指针扩展(左移)时,应该先加入位置,再更新配对(因为新加入的位置可以和区间内已有的位置配对,包括自己?但是不允许自己和自己配对?注意,新加入的位置是 `lp-1`,而区间内已有的位置包括 `lp` 到 `rp`,所以新位置可以和这些位置配对,但是不能和自己(因为自己只有一个)?所以配对数量是 `cnt[pre[lp-1]^k]`,但注意,这个数量不包括自己,因为自己还没有加入。所以加入前计算配对数量是合理的。
但是,在加入后,自己也会被计入计数,然后后续的位置就可以和自己配对。所以加入时,我们应该是先加入位置,再更新配对?不对,因为新加入的位置需要和区间内已有的位置配对,而自己还没有加入,所以已有的位置不包括自己。因此,加入时:
- 先将新位置(`lp-1`)加入计数:`cnt[pre[lp-1]]++`
- 然后,新加入的位置作为左端(或右端)可以和区间内已有的位置(包括自己?)配对?注意,区间内已有的位置包括之前加入的所有位置,但不包括自己(因为自己刚刚加入,还没有被配对计算过)。实际上,配对计算时,我们考虑的是两个不同的位置。新加入的位置 `lp-1` 可以和区间内任何一个位置(包括自己?)配对?但是,同一个位置不能配对两次。所以,新加入的位置 `lp-1` 的加入,会带来多少新增配对?就是区间内(包括自己?)等于 `pre[lp-1]^k` 的数量?但是,自己还没有加入时,区间内等于 `pre[lp-1]^k` 的数量就是 `cnt[pre[lp-1]^k]`(不包括自己),然后我们加入自己,然后这个数量就增加了?不对,应该是加入后,这个位置就可以和区间内其他等于 `pre[lp-1]^k` 的位置配对。所以,新增的配对数量就是加入前区间内 `pre[lp-1]^k` 的数量(即 `cnt[pre[lp-1]^k]`)。因此,我们应该在加入前计算这个数量,然后加入,再更新计数。
然而,标准做法是:加入一个位置时,先更新配对数量(加上加入前区间内 `pre[new]^k` 的数量),然后再将这个位置加入计数(这样后续加入的位置就可以和这个位置配对)。这样,新位置不会和自己配对(因为加入前自己不在计数数组中)。
所以,左指针扩展(左移)的正确顺序是:
```cpp
while (lp > l) {
lp--;
s += cnt[pre[lp] ^ k]; // 加入前,区间内有多少个等于pre[lp]^k的位置,这些位置都可以和lp配对
cnt[pre[lp]]++; // 将新位置加入计数
}
```
同理,右指针扩展(右移)时:
```cpp
while (rp < r) {
rp++;
s += cnt[pre[rp] ^ k]; // 加入前,区间内有多少个等于pre[rp]^k的位置
cnt[pre[rp]]++; // 加入新位置
}
```
收缩右边界(右指针左移)时:
```cpp
while (rp > r) {
cnt[pre[rp]]--; // 先移除位置
s -= cnt[pre[rp] ^ k]; // 然后减去移除后该位置能配对的个数?不对,应该是移除前,该位置能配对的个数是移除前区间内等于pre[rp]^k的个数(包括自己?)
// 但注意,移除前,该位置还在计数数组中,所以配对数量是移除前区间内等于pre[rp]^k的个数(不包括自己?因为配对要求两个不同位置,所以不会和自己配对?)
// 实际上,在移除前,该位置还在计数数组中,所以cnt[pre[rp]^k]包括其他位置,但不包括自己(因为自己不能和自己配对,所以不影响)。
// 但是,移除后,该位置被移除,那么所有以该位置为右端(或左端)的配对都将消失。这个消失的配对数量就是移除前区间内等于pre[rp]^k的数量(不包括自己)。
// 所以,应该先减去配对数量,再移除?
rp--;
}
```
然而,这里有一个问题:在移除位置 `rp` 时,我们需要减去的是以 `rp` 为右端(或左端)的配对数量。这个数量等于移除前区间内等于 `pre[rp]^k` 的位置的数量(注意,这些位置在区间内,且位置小于 `rp`(或大于,取决于定义))。但是,在移除前,`cnt[pre[rp]^k]` 已经包含了这些位置。所以,如果我们先移除,那么 `cnt[pre[rp]^k]` 就会少一个(如果 `pre[rp]^k` 等于 `pre[rp]` 的话,那么移除自己后,自己就不在计数数组中了,所以 `cnt[pre[rp]^k]` 会减少,但配对要求的是其他位置,所以实际上不应该包括自己)。因此,先移除再减配对会导致减去的数量不正确(因为移除后,计数数组已经不包含这个位置,所以 `cnt[pre[rp]^k]` 可能比移除前少(如果 `pre[rp]^k` 等于 `pre[rp]`,那么移除自己后,计数数组中自己的那一项被移除,所以 `cnt[pre[rp]]` 减少,而 `pre[rp]^k` 如果等于 `pre[rp]`,则 `cnt[pre[rp]^k]` 也减少))。所以,正确的做法是先减去配对数量,再移除:
```cpp
while (rp > r) {
// 先减去以rp为右端(或左端)的配对数量
s -= cnt[pre[rp] ^ k]; // 注意:这里可能会减去自己?不会,因为配对要求两个不同的位置,所以自己不会和自己配对。但是,如果k=0,那么pre[i]^pre[j]=0 => pre[i]=pre[j],所以位置rp可以和所有与它值相同的位置(包括自己?)配对?但是,自己不能和自己配对(因为需要两个位置,同一个位置不能作为两个端点)。所以,在计数时,位置rp加入后,它只能和它之前加入的位置(位置比它小的)配对。因此,移除时,我们减去的是其他位置与它配对的个数。
// 然而,这里有一个陷阱:如果k=0,那么配对要求两个位置的值相同,但是位置rp自己不能和自己配对(因为只有一个位置rp),所以配对的数量是其他位置的值等于pre[rp]的数量。
// 但是,在计数数组cnt中,位置rp已经被计入(因为我们还没有移除),所以cnt[pre[rp]]包括位置rp自己吗?是的,包括。所以,当我们计算 `cnt[pre[rp]^k]` 时(即 `cnt[pre[rp]]`,因为k=0),这个计数包括了位置rp自己。因此,我们减去的数量中多了一个自己(即多减了1)?这显然不对。
// 因此,为了避免这种情况,我们需要确保在计算配对数量时,不能包括自己。所以,在移除时,应该先移除自己,然后再减配对?也不行,因为移除自己后,计数数组不再包括自己,那么 `cnt[pre[rp]^k]` 就是其他位置的数量,这样减去的数量就是正确的。但是,在移除自己后,我们减去的配对数量是其他位置与位置rp的配对,这些配对在移除前是存在的,移除后不存在了,所以减去。所以顺序应该是:
// cnt[pre[rp]]--; // 先移除自己
// s -= cnt[pre[rp]^k]; // 再减去其他位置与位置rp的配对(注意,此时位置rp已经被移除,所以cnt[pre[rp]^k]中不包括rp自己,所以正确)
// 但是,这样操作后,我们发现:在移除自己后,其他位置与rp的配对数量应该是移除前其他位置中等于pre[rp]^k的数量,但移除后,这个数量并没有改变(因为移除的是rp,不是其他位置)。所以,这样操作是可行的。
// 然而,仔细考虑:在移除自己后,计数数组不再包含rp,那么 `cnt[pre[rp]^k]` 就是其他位置中等于 `pre[rp]^k` 的数量。而配对要求的是:位置j(j≠rp)满足 `pre[j]=pre[rp]^k`,这些配对在移除rp后就不存在了(因为配对需要rp这个位置)。所以,我们需要减去的数量就是移除rp后,`cnt[pre[rp]^k]` 的值(因为移除rp不影响其他位置的数量,所以移除rp后 `cnt[pre[rp]^k]` 就是其他位置中等于 `pre[rp]^k` 的数量)。
// 因此,正确的顺序是:先移除自己,再减去配对数量。
cnt[pre[rp]]--; // 先移除位置rp
s -= cnt[pre[rp] ^ k]; // 再减去配对(注意,此时位置rp已经被移除,所以cnt[pre[rp]^k]表示其他位置中等于该值的数量)
rp--;
}
```
但是,这与我们之前左指针收缩的顺序不一致。左指针收缩时,我们也是先移除自己,再减去配对?不对,我们在左指针收缩时,先移除自己,再减去配对,但这样会导致减去的配对数量不包含自己(因为自己已经被移除),所以是其他位置与自己的配对?这符合要求。
然而,这里有一个对称性:左指针收缩(移除lp)和右指针收缩(移除rp)应该采用相同的逻辑?实际上,配对是无向的,即 `pre[i]^pre[j]=k` 中,i和j没有顺序(但要求i<j)。所以,在区间内,我们统计的是所有满足条件的无序对(i,j)(i<j)。当我们移除一个位置(无论是lp还是rp)时,我们需要减去所有包含该位置的配对。这个配对数量等于移除前区间内等于 `pre[x]^k` 的位置的数量(不包括自己,因为自己不能和自己配对)。而移除后,计数数组中不再包含该位置,所以 `cnt[pre[x]^k]` 就是其他位置中等于 `pre[x]^k` 的数量,也就是我们要减去的数量。
因此,无论是左指针收缩还是右指针收缩,都应该:
- 先移除自己:`cnt[pre[x]]--`
- 然后减去 `cnt[pre[x]^k]`(此时这个值就是其他位置中等于 `pre[x]^k` 的数量)
所以,修正后的指针移动函数应该是:
```cpp
void solve(ll l, ll r, ll &ans) {
// 移动左指针:收缩(lp右移)
while (lp < l) {
cnt[pre[lp]]--;
s -= cnt[pre[lp] ^ k];
lp++;
}
// 移动左指针:扩展(lp左移)
while (lp > l) {
lp--;
cnt[pre[lp]]++;
s += cnt[pre[lp] ^ k];
}
// 移动右指针:扩展(rp右移)
while (rp < r) {
rp++;
cnt[pre[rp]]++;
s += cnt[pre[rp] ^ k];
}
// 移动右指针:收缩(rp左移)
while (rp > r) {
cnt[pre[rp]]--;
s -= cnt[pre[rp] ^ k];
rp--;
}
ans = s;
}
```
但是,这个顺序在扩展时是先加入位置,再加配对?这样会导致新加入的位置和自己配对(如果k=0)?不会,因为加入后,计数数组包含了这个位置,然后计算配对数量 `cnt[pre[x]^k]` 时,这个数量包括了这个位置吗?注意,我们加入后立即增加了计数,然后计算配对。但是,配对计算的是区间内其他位置(因为自己刚刚加入,还没有被用于配对计算?)实际上,配对计算是:新加入的位置x,它可以和区间内所有在它之前加入的位置(包括位置x自己?)配对?但位置x自己不能和自己配对(因为需要两个不同的位置)。所以,在加入位置x后,计数数组包含了x,那么 `cnt[pre[x]^k]` 就包括了位置x自己(如果 `pre[x]^k` 等于 `pre[x]`,即k=0,那么 `cnt[pre[x]]` 就包括了x自己)。所以,我们计算s时,会多算一个自己和自己配对(但实际上这是不允许的)。
因此,为了避免这种情况,扩展时应该先计算配对(加入前),再加入位置。这样,加入前,计数数组还不包含自己,所以 `cnt[pre[x]^k]` 是其他位置的数量,加入后,这个位置就可以被后续的位置配对(但不会重复计算自己)。
所以,正确的扩展顺序是:
左指针扩展(左移):
- 先计算:`s += cnt[pre[lp-1]^k]` (加入前,区间内其他位置等于 `pre[lp-1]^k` 的数量)
- 然后加入:`cnt[pre[lp-1]]++`
- 然后 `lp--`
右指针扩展(右移):
- 先计算:`s += cnt[pre[rp+1]^k]` (加入前)
- 然后加入:`cnt[pre[rp+1]]++`
- 然后 `rp++` (注意,这里我们移动rp到rp+1)
因此,修正后的指针移动函数为:
```cpp
void solve(ll l, ll r, ll &ans) {
// 移动左指针:收缩(lp右移)
while (lp < l) {
cnt[pre[lp]]--;
s -= cnt[pre[lp] ^ k];
lp++;
}
// 移动左指针:扩展(lp左移)
while (lp > l) {
lp--;
s += cnt[pre[lp] ^ k];
cnt[pre[lp]]++;
}
// 移动右指针:扩展(rp右移)
while (rp < r) {
rp++;
s += cnt[pre[rp] ^ k];
cnt[pre[rp]]++;
}
// 移动右指针:收缩(rp左移)
while (rp > r) {
cnt[pre[rp]]--;
s -= cnt[pre[rp] ^ k];
rp--;
}
ans = s;
}
```
注意:在收缩时,我们先移除再减配对(上面已经论证可行)。在扩展时,先加配对再加入。
为什么扩展时先加配对再加入?因为加入的位置还没有进入计数数组,所以此时 `cnt[pre[x]^k]` 表示的是已经存在于区间内的、等于 `pre[x]^k` 的位置的数量,这些位置都可以和新位置x配对。然后,我们将x加入计数数组,这样后续的位置就可以和x配对(避免了一次加入后立即和自己配对的情况)。
现在,考虑k=0的情况:新加入的位置x,如果区间内已经有 `pre[x]^0 = pre[x]` 的位置,那么这些位置就会和x配对。注意,这些位置是之前加入的,不包括x自己,所以不会出现自己和自己配对。
因此,这个顺序是合理的。
但是,在收缩时,我们移除位置x,然后减去 `cnt[pre[x]^k]`,这个 `cnt[pre[x]^k]` 是移除x后计数数组的值,所以它表示的是其他位置中等于 `pre[x]^k` 的数量。而我们需要减去的是移除x之前,x与这些位置的配对。在移除x前,计数数组中包含x,所以 `cnt[pre[x]^k]` 的值可能包含了x的影响吗?不会,因为 `pre[x]^k` 不一定等于 `pre[x]`,所以计数数组 `cnt[pre[x]^k]` 这个桶里,x的位置不会影响(除非 `pre[x]^k` 等于 `pre[x]`,即k=0)。当k=0时,`pre[x]^k = pre[x]`,那么在移除x前,计数数组中 `pre[x]` 的计数包括x,而移除后,计数减1,所以 `cnt[pre[x]]` 减少了1。因此,在移除后,我们减去的 `cnt[pre[x]]` 比移除前少1(即少了自己)。而我们需要减去的配对数量是移除前其他位置中等于 `pre[x]` 的数量(即不包括x),所以移除后 `cnt[pre[x]]` 的值(等于移除前 `cnt[pre[x]]-1`)就是其他位置的数量,这正是我们想要的。
所以,收缩的顺序(先移除,再减配对)是正确的。
综上所述,我们采用以下顺序:
扩展(左移lp):
lp--
s += cnt[pre[lp] ^ k]
cnt[pre[lp]]++
扩展(右移rp):
rp++
s += cnt[pre[rp] ^ k]
cnt[pre[rp]]++
收缩(右移lp):
cnt[pre[lp]]--
s -= cnt[pre[lp] ^ k]
lp++
收缩(左移rp):
cnt[pre[rp]]--
s -= cnt[pre[rp] ^ k]
rp--
注意:收缩时,先移除再减配对;扩展时,先加配对再加入。
另外,初始状态:我们设区间为 [0,0],即 lp=0, rp=0,且 cnt[pre[0]]=1。
最后,我们还需要注意:在扩展和收缩过程中,配对的数量s可能会因为k=0而出现负数?不会,因为配对数量是累积的。
下面我们用题目中的例子验证:
输入:n=4, m=5, k=1, a=[1,2,3,1]
前缀和:
pre[0]=0
pre[1]=1
pre[2]=1^2=3
pre[3]=3^3=0
pre[4]=0^1=1
查询(注意,我们已经将查询的 l 减1,所以查询区间为 [l, r](前缀和数组的下标)):
查询1: [0,4] -> l=0, r=4
查询2: [0,3] -> l=0, r=3
查询3: [1,3] -> l=1, r=3
查询4: [1,4] -> l=1, r=4
查询5: [3,4] -> l=3, r=4
初始状态:lp=0, rp=0, cnt[pre[0]]=1, s=0 (因为区间内只有一个位置0,无法配对,所以s=0)
处理第一个查询 [0,4]:
需要扩展右指针到4:
rp=0->1:
rp++ -> rp=1
s += cnt[pre[1]^k] = cnt[1^1]=cnt[0]=1 -> s=1
cnt[pre[1]]++ -> cnt[1] = 1 (原来0:1, 1:1)
rp=1->2:
rp=2
s += cnt[pre[2]^k] = cnt[3^1]=cnt[2]=0 -> s=1
cnt[pre[2]]++ -> cnt[3]=1
rp=2->3:
rp=3
s += cnt[pre[3]^k] = cnt[0^1]=cnt[1]=1 -> s=2
cnt[pre[3]]++ -> cnt[0]=2
rp=3->4:
rp=4
s += cnt[pre[4]^k] = cnt[1^1]=cnt[0]=2 -> s=4
cnt[pre[4]]++ -> cnt[1]=2
所以第一个查询的答案是4。
第二个查询 [0,3]:
需要收缩右指针从4到3:
rp=4->3:
cnt[pre[4]]-- -> cnt[1] becomes 1
s -= cnt[pre[4]^k] = cnt[1^1]=cnt[0]=2 -> s=4-2=2
rp=3
所以答案是2。
第三个查询 [1,3]:
需要收缩左指针从0到1:
lp=0->1:
cnt[pre[0]]-- -> cnt[0]=1
s -= cnt[pre[0]^k] = cnt[0^1]=cnt[1]=1 -> s=2-1=1
lp=1
所以答案是1。
第四个查询 [1,4]:
需要扩展右指针从3到4:
rp=3->4:
rp=4
s += cnt[pre[4]^k] = cnt[1^1]=cnt[0]=1 -> s=1+1=2 (注意:此时区间为[1,4],计数数组:cnt[0]=1, cnt[1]=1, cnt[3]=1, 因为pre[4]=1,所以cnt[1]在加入前为1(因为之前移除了pre[0]和pre[4]?实际上,当前区间是[1,3],所以计数数组:pre[1]=1, pre[2]=3, pre[3]=0 -> cnt[1]=1, cnt[3]=1, cnt[0]=1。然后加入pre[4]=1,所以加入前,cnt[pre[4]^k]=cnt[0]=1,所以s+=1)
cnt[pre[4]]++ -> cnt[1]=2
所以答案是2。
第五个查询 [3,4]:
需要收缩左指针从1到3:
lp=1->2:
cnt[pre[1]]-- -> cnt[1]=1
s -= cnt[pre[1]^k] = cnt[1^1]=cnt[0]=1 -> s=2-1=1
lp=2
lp=2->3:
cnt[pre[2]]-- -> cnt[3]=0
s -= cnt[pre[2]^k] = cnt[3^1]=cnt[2]=0 -> s=1
lp=3
所以答案是1。
输出:4,2,1,2,1 符合预期。
因此,最终代码采用上述指针移动顺序。
注意:在修正代码中,我们还需要注意数组大小。因为异或值最大可能达到 2^17-1(因为a[i]<=1e5,而1e5<2^17=131072),所以cnt数组的大小应该至少为 2^17(131072)的两倍?为了保险,可以设置为N*2+10(因为N=200000,所以最大异或值不会超过200000?不对,异或值可能达到(1<<17)-1,即131071,所以cnt数组大小至少为131072*2?实际上,题目中a[i]和k都在0~1e5之间,所以异或值最大约为130000多,我们可以设置cnt数组大小为200000(即2e5)的两倍,或者直接设为300000。
由于题目中a[i]<=1e5,n<=1e5,所以前缀和数组的值最大不超过2^17-1(131071),所以我们可以将cnt数组大小设为132000(或200000)。
在修正代码中,我们定义 `const ll M = 2e5 + 1000;` 然后 `cnt` 数组大小为 `M`。
修改后的完整代码:
注意:原代码中 `cnt` 数组的大小为 `N+5`,而 `N=2e5`,所以 `cnt` 数组大小约为200000,足够(因为异或值最大131071<200000)。
因此,我们只需要修改指针移动的顺序,并确保初始状态正确。
最终代码(基于原代码风格):</think>在异或序列问题中,使用莫队算法统计子区间时,需要正确理解前缀和与配对的计数逻辑。以下是关键点解析和修正后的代码:
### 关键问题分析
1. **配对计数原理**:
- 子区间 `[x,y]` 的异或和 = `pre[y] ^ pre[x-1]`
- 统计 `pre[j] ^ pre[i] = k` 的配对数量(`i < j`)
- 移动指针时,需计算位置变化带来的配对增减
2. **指针移动逻辑**:
- **收缩时**(移除位置):
- 先移除当前位置的计数
- 再减去该位置产生的配对(避免自配对)
- **扩展时**(加入位置):
- 先计算新增配对(避免自配对)
- 再加入当前位置计数
3. **初始状态**:
- 包含 `pre[0]=0`,确保子区间 `[1,y]` 可被统计
- 查询区间映射为前缀和下标 `[l-1, r]`
### 修正后代码
```cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll N = 2e5;
struct node {
ll l, r, f, ans;
} b[N + 5];
ll n, m, k;
ll blk[N + 5], lenb;
ll a[N + 5], pre[N + 5];
ll cnt[N * 2 + 5]; // 扩大防止值域溢出
ll lp, rp, s;
// 莫队排序优化(奇偶排序)
bool cmp(node l1, node l2) {
if (blk[l1.l] == blk[l2.l]) {
if (blk[l1.l] & 1) return l1.r < l2.r;
return l1.r > l2.r;
}
return blk[l1.l] < blk[l2.l];
}
bool cmpf(node l1, node l2) {
return l1.f < l2.f;
}
void solve(ll l, ll r, ll &ans) {
// 左指针收缩(右移)
while (lp < l) {
cnt[pre[lp]]--;
s -= cnt[pre[lp] ^ k];
lp++;
}
// 左指针扩展(左移)
while (lp > l) {
lp--;
s += cnt[pre[lp] ^ k];
cnt[pre[lp]]++;
}
// 右指针扩展(右移)
while (rp < r) {
rp++;
s += cnt[pre[rp] ^ k];
cnt[pre[rp]]++;
}
// 右指针收缩(左移)
while (rp > r) {
cnt[pre[rp]]--;
s -= cnt[pre[rp] ^ k];
rp--;
}
ans = s;
}
int main() {
scanf("%lld%lld%lld", &n, &m, &k);
// 分块设置
lenb = max(1LL, (ll)sqrt(n));
for (ll i = 0; i <= n; i++) {
blk[i] = i / lenb;
}
// 前缀和初始化
pre[0] = 0;
for (ll i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
pre[i] = pre[i - 1] ^ a[i];
}
// 存储查询(调整左边界)
for (ll i = 1; i <= m; i++) {
scanf("%lld%lld", &b[i].l, &b[i].r);
b[i].l--; // 映射到前缀和区间 [l-1, r]
b[i].f = i;
}
// 莫队排序
sort(b + 1, b + m + 1, cmp);
// 初始化区间 [0,0]
lp = 0; rp = 0;
s = 0;
cnt[pre[0]] = 1; // 包含pre[0]
// 处理查询
for (ll i = 1; i <= m; i++) {
solve(b[i].l, b[i].r, b[i].ans);
}
// 按原顺序输出
sort(b + 1, b + m + 1, cmpf);
for (ll i = 1; i <= m; i++) {
printf("%lld\n", b[i].ans);
}
return 0;
}
```
### 修正点详解
1. **前缀和映射**:
- 查询区间 `[l, r]` → 前缀和下标 `[l-1, r]`
- 通过 `b[i].l--` 实现转换
2. **指针移动顺序**:
```cpp
// 收缩(移除位置)
cnt[pre[pos]]--; // 先移除
s -= cnt[pre[pos]^k]; // 后减配对
// 扩展(加入位置)
s += cnt[pre[pos]^k]; // 先加配对
cnt[pre[pos]]++; // 后加入
```
3. **初始状态**:
- 区间 `[0,0]` 包含 `pre[0]=0`
- `cnt[pre[0]] = 1` 初始化
4. **值域扩展**:
- `cnt` 数组大小设为 `N*2+5`(异或值最大约200,000)
5. **奇偶排序优化**:
```cpp
if (blk[l1.l] & 1) return l1.r < l2.r;
else return l1.r > l2.r;
```
### 示例验证
输入:
```
4 5 1
1 2 3 1
1 4
1 3
2 3
2 4
4 4
```
输出:
```
4
2
1
2
1
```
完全匹配题目要求。
---