题目
题目背景
小游戏:“吾之
A
K
AK
AK,已至大限,若再
A
K
AK
AK,一字不足以概也;校长宝座,彼可取而代之。”
没事,我不担心字数。下一次就写成《小游戏叕又AK了!》
题目描述
一山难容二虎!除非一公一母,除非是校长和校监。
正好校长看到这样一个位运算:与非。记为
n
a
n
d
\mathrm{nand}
nand 。运算规则是
a
nand
b
=
not
(
a
and
b
)
a\operatorname{nand}b=\operatorname{not}(a\operatorname{and}b)
anandb=not(aandb)
其中
n
o
t
\rm not
not 是按位取反,而
a
n
d
\mathrm{and}
and 是按位与。它就很好的描述了
D
D
(
X
Y
X
)
\sf DD(XYX)
DD(XYX) 和
O
U
Y
E
\sf OUYE
OUYE 的处境:二人都在场,大家就不知道该受到谁的辖制;其一在场,大家都不敢大声说话;二者都不在,大家都害怕,校监在把什么报告给校长!
于是校长很喜欢它。校长写下了一个
0
/
1
0/1
0/1 串,表示每个时刻他是否在别人家恬静地坐着,在沉默无声中增进同事的感情。他打算对于一个区间内的每个前缀求出
n
a
n
d
\rm nand
nand 和,然后求它们的异或值。即,记
principal
(
l
,
r
)
=
a
l
nand
a
l
+
1
nand
⋯
nand
a
r
\operatorname{principal}(l,r)=a_l\operatorname{nand}a_{l+1}\operatorname{nand}\cdots\operatorname{nand}a_r
principal(l,r)=alnandal+1nand⋯nandar(从左往右计算),那么要求
⨁
i
=
L
R
principal
(
L
,
i
)
\bigoplus_{i=L}^{R}\operatorname{principal}(L,i)
i=L⨁Rprincipal(L,i)
这里当然把
nand
\operatorname{nand}
nand 被定义为了
{
0
,
1
}
\{0,1\}
{0,1} 上的。也就是说,可以把位运算看成逻辑运算。
校长要利用这些数据,决定接下来他要不要去同事家里坐两个钟头。你能帮助思维能力退化到了前所未有的境地的校长,让他不被这种小问题击垮吗?
注意校长会发出两种指令:让你说出上面的问题的答案,和在当前的 a a a 序列后面附加一个数。
数据范围与提示
校长只会问
q
=
1
0
5
q=10^5
q=105 个问题,但是他有
n
=
4
×
1
0
6
n=4\times 10^6
n=4×106 次上门。
强制在线,上一次询问的结果会影响接下来的 a a a 序列插入,和下一次询问的区间。
T i p s ( n o t a v a i l a b l e i n y o u r p r e f e r r e d l a n g u a g e : s i m p l i f i e d C h i n e s e ) \rm Tips\;(not\;available\;in\;your\;preferred\;language:simplified\;Chinese) Tips(notavailableinyourpreferredlanguage:simplifiedChinese)
- F r e n c h \rm French French: Quelqu’un a vraiment traduit cette phrase inutile? \text{Quelqu'un a vraiment traduit cette phrase inutile?} Quelqu’un a vraiment traduit cette phrase inutile?
- G e r m a n \rm German German: Und nach dem ersten Fehlschlag erwarten Sie immer noch etwas Nettes? \text{Und nach dem ersten Fehlschlag erwarten Sie immer noch etwas Nettes?} Und nach dem ersten Fehlschlag erwarten Sie immer noch etwas Nettes?
- J a p a n e s e \rm Japanese Japanese: これは本物です:オンラインクエリ、“オフライン”の変更 \text{これは本物です:オンラインクエリ、“オフライン”の変更} これは本物です:オンラインクエリ、“オフライン”の変更
思路
我的思路
我的思路总是很奇怪。为什么奇葩的事老是发生在你身上啊?是因为你是一个奇葩的人吗?
注意到 n a n d \rm nand nand 操作是只有 1 , 1 1,1 1,1 会得到 0 0 0,其余都得到 1 1 1 的。假设 n a n d ( l , r − 1 ) = 1 \mathrm {nand}(l,r-1)=1 nand(l,r−1)=1,那么下一位如果是 0 0 0,就仍然保持 n a n d ( l , r ) = 1 \mathrm{nand}(l,r)=1 nand(l,r)=1,否则变成 0 0 0,这必然使得 n a n d ( l , r + 1 ) = 1 \mathrm{nand}(l,r+1)=1 nand(l,r+1)=1,于是又进入这种形式。
那么,用 f ( i ) f(i) f(i) 表示前面得到一个 1 1 1 的时候,一路往后,所有 n a n d \rm nand nand 结果的异或和,则有 f ( i ) = { f ( i + 1 ) ⊕ 1 ( t h i s = 0 ) f ( i + 2 ) ⊕ 1 ( t h i s = 1 ) f(i)=\begin{cases}f(i+1)\oplus 1&(this=0)\\f(i+2)\oplus 1&(this=1)\end{cases} f(i)={f(i+1)⊕1f(i+2)⊕1(this=0)(this=1) 。
那么,每个 0 0 0 向 i + 1 i+1 i+1 连边,每个 1 1 1 向 i + 2 i+2 i+2 连边,我们将得到一棵树。问题转化为了树上路径长度!
注意最后一步可能是走到 r − 1 r-1 r−1,此时恰好 t h i s = 1 this=1 this=1,就会越过 r r r 。这种情况很容易判断,只需要看看 r − 1 r-1 r−1 结尾的最长全 1 1 1 子串是奇数还是偶数长度。因为 0 , 1 0,1 0,1 出现时,不可能一步跨过二者,就一定会走到那个 1 1 1 上面去;剩下的全是 1 1 1,就直接数奇偶性。
如何求树上路径长度?注意这里的路径一定是一条链(祖先和子孙的关系),所以我们只需要维护每个点的深度。分几种情况讨论:
- 如果结尾是 0 , t h i s 0,this 0,this,那么所有点都必然走到 t h i s this this,直接打全局 d e p + 1 dep+1 dep+1 的标记;
- 如果结尾是 0 , 1 , t h i s 0,1,this 0,1,this,所有点都必然走不到 t h i s this this,因为它没有入边,所以不作任何修改;
- 如果结尾是 1 , 1 , t h i s 1,1,this 1,1,this,似乎有一些点是能走到 t h i s this this 的呢……
着重讨论最后这种情况。仔细想想会发现,这两个 “子树” 其中之一是一条链!
不信,你就把第一个 0 0 0 找到。那么就是 0 , 1 , [ 1 ] , … , 1 , t h i s 0,1,[1],\dots,1,this 0,1,[1],…,1,this 。第二个 [ 1 ] [1] [1] 是没有入度的!并且往后一直是 1 1 1,就是下标奇偶性固定的一个区间,接成一条链!
那么我们可以用两个树状数组(代表下标为奇或偶),维护区间加。
时间复杂度 O [ ( n + q ) log n ] \mathcal O[(n+q)\log n] O[(n+q)logn],好像比较危险?计算一下常数。每次查询需要 2 2 2 次 B I T \tt BIT BIT 操作,而每个插入需要最多 2 2 2 次 B I T \tt BIT BIT 操作。同时 B I T \tt BIT BIT 操作每次是约 log n 2 \frac{\log n}{2} 2logn 的(因为操作次数是 x x x 二进制下 1 1 1 的数量,每一位只提供 1 2 \frac{1}{2} 21 的期望),所以常数约为 1 1 1,挺快的。
有什么优化点吗?比如说, B I T \tt BIT BIT 修改操作是单调的(从左到右)。然而也并不会。
正解思路
只要把上面的做法扩展一点, f ( i , ⟨ a ⟩ ) f(i,\langle a\rangle) f(i,⟨a⟩) 表示第一个数字是 i i i 时,与 ⟨ a ⟩ \langle a\rangle ⟨a⟩ 求 n a n d \rm nand nand 之后得到什么。这样显然就让线段树区间可以合并了嘛!再维护一个 g ( i , ⟨ a ⟩ ) g(i,\langle a\rangle) g(i,⟨a⟩) 表示 n a n d \rm nand nand 结果的异或和,搞定。(这玩意儿可以推广到任意位运算,甚至是任意简单函数。)
如果每次暴力修改线段树,还是 O ( n log n ) \mathcal O(n\log n) O(nlogn) 的。可是线段树如果是 b u i l d build build 就是 O ( n ) \mathcal O(n) O(n) 了!没错,进行一次修改的时候,如果某个节点的区间还没填满,就不要继续往上更新了,反正查询也不会用到。这样就是 O ( n ) \mathcal O(n) O(n) 次线段树修改了!
时间复杂度
O
(
n
+
q
log
n
)
\mathcal O(n+q\log n)
O(n+qlogn) 。那就是 “在线询问,离线修改” 的含义。话说有人看提示吗。
代码
官方题解的做法可能比较简单,我干脆贴我的代码好了。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void writeint(int_ x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) writeint(x/10);
putchar((x-x/10*10)^48);
}
const int MaxN = 4000005;
const int MaxM = MaxN>>1;
bool c[2][MaxM]; // even/odd BIT
int two[MaxN], one[MaxN];
bool dep[MaxN], copi[MaxN];
void addBit(int d,int id){
id = (id+1)>>1;
for(int i=id; i<MaxM; i+=(i&-i))
c[d][i] ^= 1; // only 1
}
bool askBit(int d,int id){
bool res = 0; id = (id+1)>>1;
for(int i=id; i; i&=(i-1))
res ^= c[d][i];
return res;
}
int main(){
bool all = 0, lst = 0;
int T = readint(), n = 0;
for(int opt,x; T; --T){
opt = readint(), x = readint();
/* insert */ ;
if(opt == 1){
copi[++ n] = (x ^= lst);
/* update one */ ;
if(!x) one[n] = 0;
else one[n] = one[n-1]+1;
/* four types */ ;
if(!one[n-1]) all ^= 1;
else if(one[n-1] == 1)
two[n] = 1; // isolated
else{ // worst type
if(two[n-2]){ // chain
two[n] = two[n-2]+1;
addBit(n&1,n-2*(two[n]-1));
}
else{
all ^= 1; // except that chain
addBit((n&1)^1,n-2*two[n-1]+1);
}
}
bool t = askBit(n&1,n);
dep[n] = t^all; // init = 0
}
else{
int y = readint();
if(lst){
y = n+1-y, x = n+1-x;
x ^= y ^= x ^= y;
}
if(x == y){
lst = copi[x];
putchar(lst^48);
putchar('\n');
continue; // done
}
if(x == y-1){
lst = !(copi[x]&copi[y]);
lst ^= copi[x]; // range(x,x)
putchar(lst^48);
putchar('\n');
continue; // done
}
if(copi[x] == 1) ++ x;
else x += 2; // 0 and 1
/** @note remember to add 1 to ans! */ ;
lst = copi[y]; // if it ends with 1, minus 1
if(one[y-1] >= y-x){ // cover
if((y^x)&1) -- y, lst = 0;
}
else if(one[y-1]&1) -- y, lst = 0;
lst ^= askBit(x&1,x)^dep[x];
lst ^= askBit(y&1,y)^dep[y];
/* cnt = edge^1 but init it's 1 */ ;
putchar(lst^48), putchar('\n');
}
}
return 0;
}