[ZJOI2017]树状数组

题目

传送门 to luogu

传送门 to LOJ

思路

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 ab 时, 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 ba 。所以我们大胆地说:打错的芬威克树 查询的是后缀和。只有一个例外,就是 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=l1r1ai,而实际上我们需要的值是 ⨁ 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=lr1ai 异或上两个不同的数。所以二者相等的充要条件是
a l − 1 = a r a_{l-1}=a_r al1=ar

问题转化完了。考虑求解这个问题:有多大的概率使 x , y x,y x,y 最终的值相同。即,二者操作次数的差值为偶数。对于一个操作(设区间长度为 λ \lambda λ,方便书写),如果 l − 1 l-1 l1 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(xy) 的答案。当 y y y 遇到一个区间 [ L , R ] [L,R] [L,R] 时(即 y = L y=L y=L 时),将 [ 1 , L − 1 ] [1,L-1] [1,L1] 增加一个 “覆盖其中之一”,将 [ 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,L1] 去掉一个 “覆盖其中之一”,将 [ 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 tlowbit(t)<timet)的所有 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值