题目
思路
记
D
(
x
)
D(x)
D(x) 为 x-=lowbit(x)
循环可以访问到的点,记
U
(
x
)
U(x)
U(x) 为 x+=lowbit(x)
可以访问到的点。即,正常的 树状数组中,
D
(
x
)
D(x)
D(x) 是 find
访问到的点,而
U
(
x
)
U(x)
U(x) 是 add
访问到的点。
我们知道正常的树状数组可以维护前缀和。那就是说,当且仅当
a
≤
b
a\le b
a≤b 时,
U
(
a
)
∩
D
(
b
)
U(a)\cap D(b)
U(a)∩D(b) 大小为
1
1
1(即恰好有一个点是二者均访问过的),所以 find(b)
才会统计 add(a)
的值嘛。
这道题里,如果 find(b)
想统计到 add(a)
,就需要
U
(
b
)
∩
D
(
a
)
≠
Ø
U(b)\cap D(a)\ne\text{\O}
U(b)∩D(a)=Ø 才行。这就是说,
b
≤
a
b\le a
b≤a 。所以我们大胆地说:打错的芬威克树 查询的是后缀和。只有一个例外,就是 find(0)
(出题人狡猾地判断了死循环)!
抛开那种特殊情况,我们发现
q
u
e
r
y
(
l
,
r
)
=
⨁
i
=
l
−
1
r
−
1
a
i
{\rm query}(l,r)=\bigoplus_{i=l-1}^{r-1}a_i
query(l,r)=⨁i=l−1r−1ai,而实际上我们需要的值是
⨁
i
=
l
r
a
i
\bigoplus_{i=l}^{r}a_i
⨁i=lrai 。瞪眼法观察一下,无非就是
⨁
i
=
l
r
−
1
a
i
\bigoplus_{i=l}^{r-1}a_i
⨁i=lr−1ai 异或上两个不同的数。所以二者相等的充要条件是
a
l
−
1
=
a
r
a_{l-1}=a_r
al−1=ar
问题转化完了。考虑求解这个问题:有多大的概率使 x , y x,y x,y 最终的值相同。即,二者操作次数的差值为偶数。对于一个操作(设区间长度为 λ \lambda λ,方便书写),如果 l − 1 l-1 l−1 和 r r r 都在它的范围内,那么有 2 λ \frac{2}{\lambda} λ2 的概率让二者操作次数的差值(下简写为 d i f dif dif)奇偶性变化。如果只有一个在它的范围内,那么有 1 λ 1\over \lambda λ1 的概率翻转 d i f dif dif 。其他情况都不带来任何影响。
需要求解区间覆盖问题,我比较喜欢 扫描线。将 y y y 不断右移,维护所有 x ( x ≤ y ) x\;(x\le y) x(x≤y) 的答案。当 y y y 遇到一个区间 [ L , R ] [L,R] [L,R] 时(即 y = L y=L y=L 时),将 [ 1 , L − 1 ] [1,L-1] [1,L−1] 增加一个 “覆盖其中之一”,将 [ L , R ] [L,R] [L,R] 增加一个 “覆盖二者” 。当 y y y 离开这个区间时(即 y = R + 1 y=R+1 y=R+1 时),将 [ 1 , L − 1 ] [1,L-1] [1,L−1] 去掉一个 “覆盖其中之一”,将 [ L , R ] [L,R] [L,R] 去掉一个 “覆盖二者”,增添一个 “覆盖其中之一” 。
怎么去掉标记呢?我们可以用 “复数”
x
+
i
y
x+iy
x+iy 作为信息,其中
i
2
=
1
i^2=1
i2=1 。现实含义就是,
y
y
y 存储
d
i
f
dif
dif 被翻转的概率,翻转两次就等于不翻转,就会进入
x
x
x 里面。那么我们乘一个逆元
a
+
i
b
a+ib
a+ib,只需要满足
{
a
x
+
b
y
=
1
a
y
+
b
x
=
0
\begin{cases}ax+by=1 \\ ay+bx=0\end{cases}
{ax+by=1ay+bx=0
由于
x
+
y
=
1
x+y=1
x+y=1,所以只有一种情况没有
a
,
b
a,b
a,b 存在,那就是
x
=
y
=
1
2
x=y=\frac{1}{2}
x=y=21 。把它特判掉!因为
(
1
2
+
1
2
i
)
2
=
1
2
+
1
2
i
\left(\frac{1}{2}+\frac{1}{2}i\right)^2=\frac{1}{2}+\frac{1}{2}i
(21+21i)2=21+21i
所以只需要记录一下,这种特殊的 “复数” 有多少个,查询的时候,如果至少有一个,那就乘一次。
然后新的问题出现了:操作有先后顺序。也就是说,每个区间只对
t
i
m
e
time
time 轴上的一个后缀起效。其实也没什么大不了,用一个数据结构维护一下
t
i
m
e
time
time 轴就行。比如
B
I
T
\tt BIT
BIT 。而
B
I
T
\tt BIT
BIT 的每个节点是一个线段树,维护
t
i
m
e
time
time 在
B
I
T
\tt BIT
BIT 的区间中(即
t
−
l
o
w
b
i
t
(
t
)
<
t
i
m
e
≤
t
t-{\rm lowbit}(t)<time\le t
t−lowbit(t)<time≤t)的所有 add
操作。
查询的时候,要对应到 log n \log n logn 个 B I T \tt BIT BIT 的节点,每个节点用 log n \log n logn 去查询 “复数” 并相乘,复杂度 O ( log 2 n ) \mathcal O(\log^2n) O(log2n) 。修改同理。
——不愧是强省 Z J \sf ZJ ZJ 啊!竟然还有 l = 1 l=1 l=1 要考虑!此时询问的是, ⨁ i = 1 r a i \bigoplus_{i=1}^{r}a_i ⨁i=1rai 和 ⨁ i = r n a i \bigoplus_{i=r}^{n}a_i ⨁i=rnai 是否相等。其实也是类似的,如果 r r r 在范围内,那么有 1 λ 1\over\lambda λ1 的概率不翻转,否则必定翻转。这个可以直接 [ L , R ] [L,R] [L,R] 区间修改。
代码
然而,我食言了。我用外层线段树维护了 [ L , R ] [L,R] [L,R],内部是一个动态开点线段树,维护 t i m e time time 轴。为啥这样做?因为 t i m e time time 互不相同,修改都是单点修改,可以直接赋值,避免了求逆元的烦恼。
这样一来,就不能进行 pushDown
了,因为标记无法合并。所以说,需要 标记永久化。
然后在 l u o g u \tt luogu luogu 上 M L E MLE MLE 了……没救了,就贴这份代码了。 L O J \tt LOJ LOJ 上可以过。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <forward_list>
using namespace std;
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;
}
const int Mod = 998244353;
struct Comp{
int x; // x + i * (1 - x)
/** constructor won't mod, be cautious */
Comp(int X=1):x(X){ }
int operator[](const int &t) const {
return t ? (Mod+1-x)%Mod : x;
}
Comp operator = (const int &X){
x = X%Mod; return *this;
}
Comp operator * (const Comp &t) const {
return Comp((1ll*x*t.x+1ll
*(Mod+1-x)*t[1])%Mod);
}
Comp operator *= (const Comp &t){
return *this = (*this)*t;
}
void imag(const int &X){
x = (Mod+1-X%Mod)%Mod;
}
};
const int MaxN = 100005;
int n, m; // length and time
namespace Dynamic{
const int MaxM = 50000000;
Comp v[MaxM]; int son[MaxM][2];
int cntNode; // dynamic allocate
# define LSON son[o][0],l,(l+r)>>1
# define RSON son[o][1],((l+r)>>1)+1,r
void modify(int qid,Comp t,int &o,int l=1,int r=m){
if(!o) o = ++ cntNode; // new node
if(l == r) return void(v[o] = t);
if(qid <= (l+r)>>1)
modify(qid,t,LSON);
else modify(qid,t,RSON);
v[o] = v[son[o][0]]*v[son[o][1]];
}
Comp query(int qr,int o,int l=1,int r=m){
if(!o || qr < l) return Comp();
if(r <= qr) return v[o]; // involved
return query(qr,LSON)*query(qr,RSON);
}
# undef LSON
# undef RSON
}
struct SgTree{
int rt[MaxN<<2];
# define LSON o<<1,l,(l+r)>>1
# define RSON o<<1|1,((l+r)>>1)+1,r
void modify(int ql,int qr,Comp t,int T,int o=1,int l=1,int r=n){
if(qr < l || r < ql) return ;
if(ql <= l && r <= qr)
return Dynamic::modify(T,t,rt[o]);
modify(ql,qr,t,T,LSON), modify(ql,qr,t,T,RSON);
}
Comp query(int qid,int T,int o=1,int l=1,int r=n){
Comp res = Dynamic::query(T,rt[o]);
if(l == r) return res; // no more to get
if(qid <= (l+r)>>1) res *= query(qid,T,LSON);
else res *= query(qid,T,RSON); return res;
}
# undef LSON
# undef RSON
};
SgTree tre, sing;
struct Query{
int l, r, id, opt;
bool operator < (const Query &t) const {
if(r != t.r) return r < t.r;
return id < t.id; // time order
}
};
Query ask[MaxN];
forward_list<int> asr[MaxN], asl[MaxN];
int inv[MaxN], ans[MaxN];
int main(){
n = readint(); inv[1] = 1;
for(int i=2; i<=n; ++i)
inv[i] = (0ll+Mod-Mod/i)*inv[Mod%i]%Mod;
m = readint(); Comp t;
for(int i=1; i<=m; ++i){
ask[i].opt = readint();
ask[i].l = readint();
ask[i].r = readint(), ask[i].id = i;
if(ask[i].opt == 2) continue;
ans[i] = -1; // modify no answer
t = inv[ask[i].r-ask[i].l+1];
sing.modify(ask[i].l,ask[i].r,t,i);
t = 0; // must be flipped once
if(ask[i].l != 1)
sing.modify(1,ask[i].l-1,t,i);
if(ask[i].r != n)
sing.modify(ask[i].r+1,n,t,i);
}
sort(ask+1,ask+m+1);
for(int i=1; i<=m; ++i){
if(ask[i].opt == 2) continue;
asl[ask[i].l].push_front(i);
asr[ask[i].r].push_front(i);
}
for(int r=1,i=1; r<=n; ++r){
for(auto y : asl[r]){
/* only cover r */ ;
t.imag(inv[ask[y].r-ask[y].l+1]);
if(ask[y].l != 1) // avoid stuck
tre.modify(1,ask[y].l-1,t,ask[y].id);
/* both in range */ ;
t.imag(t[1]<<1); // automatically mod
tre.modify(ask[y].l,ask[y].r,t,ask[y].id);
}
/* deal with queries */ ;
for(; i<=m&&ask[i].r==r; ++i)
if(ask[i].opt == 2){
if(ask[i].l == 1)
ans[ask[i].id] = sing.query(
ask[i].r,ask[i].id)[0];
else ans[ask[i].id] = tre.query(
ask[i].l-1,ask[i].id)[0];
}
for(auto y : asr[r]){
/* no more cover r, only l */ ;
t.imag(inv[ask[y].r-ask[y].l+1]);
tre.modify(ask[y].l,ask[y].r,t,ask[y].id);
/* no more cover r, nothing */ ;
tre.modify(1,ask[y].l-1,Comp(),ask[y].id);
}
}
for(int i=1; i<=m; ++i)
if(~ans[i]) printf("%d\n",ans[i]);
return 0;
}