把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷7470】[NOI Online 2021 提高组] 岛屿探险(线段树分治+Trie树)

点此看题面

  • 有一个长度为\(n\)的序列,每个位置有两个属性\(a_i,b_i\)
  • \(q\)次询问,每次给定一个区间和两个属性\(c_j,d_j\),询问区间中有多少位置满足\((a_i\oplus c_j)\le\min\{b_i,d_j\}\)
  • \(n,q\le10^5,a,b,c,d<2^{24}\)

NOI Online的时候花了近三个小时肝\(T1\)\(60\)分,导致这道题口胡出来却来不及写了,最后由于服务器爆炸连暴力都没来得及交上去。

结果没想到今天花了半个多小时就把这题写完了,而且过了样例后直接一遍过,如今想来真是血亏。

不过反正我开O2爆零了,似乎没什么区别?

线段树分治

首先由于这是一个区间询问,搞起来就非常麻烦。

显然需要离线转化一下,官方题解给出的是\(CDQ\)分治,但这还需要经过进一步推导。

因此,像我这种暴力选手就直接无脑上线段树分治了,这样一来就能把区间询问变成全局询问了。

但实测线段树分治应该比\(CDQ\)分治效率要低一些吧。

\(Trie\)树乱搞

反正我们已经把询问离线了,不如进一步离线,当枚举到线段树上一个节点的时候,把所有位置按照\(b_i\)排序,所有询问按照\(d_j\)排序。

然后我们双指针去枚举,就能对于每个询问,把位置划分为\(b_i<d_j\)\(b_i\ge d_j\)的两部分,每个位置只会从第二部分进入第一部分一次,然后对于这两部分的询问分别去处理。

第一部分\((a_i\oplus c_j)\le b_i\),考虑在\(Trie\)树上表示出\(c_j\)可能的取值区间,询问时就可以直接求了。

因为比大小肯定比较最高的不同位,所以对于每一位我们只要假设先前的取值都相同,否则就已经比出结果了。

然后一种情况是它们仍旧取值相同,那么递归处理。

另一种情况是它们取值不同,如果\(b\)这一位是\(1\)则合法,否则反变成了\(a>b\)肯定不合法。因此只要在\(b\)这一位为\(1\)的时候给对应的子树打个永久化标记即可。

则一次询问就是求出从根到\(c_j\)所有点标记之和。

第二部分\((a_i\oplus c_j)\le d_j\),其实刚好和上面相反,我们在\(Trie\)树上插入\(a_i\),询问时去找它的可能区间。

同理考虑这一位的取值,如果相同则递归处理;如果不同且\(d\)这一位是\(1\),给答案加上对应子树的总和即可。

代码:\(O(nlognlogV)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define D 24
using namespace std;
int n,Qt,a[N+5],b[N+5],ans[N+5];I bool cmp(CI x,CI y) {return b[x]<b[y];}
struct Q {int p,c,d;I Q(CI i=0,CI x=0,CI y=0):p(i),c(x),d(y){}I bool operator < (Con Q& o) Con {return d<o.d;}};
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
class Trie
{
	private:
		#define PU(x) (O[x].V2=O[O[x].S[0]].V2+O[O[x].S[1]].V2)
		int Nt;struct node {int V1,V2,S[2];}O[N*D*3];
	public:
		I void Cl() {W(Nt) O[Nt].V1=O[Nt].V2=O[Nt].S[0]=O[Nt].S[1]=0,--Nt;}
		I void A1(int& rt,CI a,CI b,CI v,CI p=D-1)//插入a,b表示出c的可能取值
		{
			if(!~p) return (void)(O[rt].V1+=v);!O[rt].S[0]&&(O[rt].S[0]=++Nt),!O[rt].S[1]&&(O[rt].S[1]=++Nt);//建好子节点
			RI t=(a^b)>>p&1;b>>p&1&&(O[O[rt].S[t^1]].V1+=v),A1(O[rt].S[t],a,b,v,p-1),PU(rt);//如果b这位为1可以不同,相同情况递归处理
		}
		I int Q1(CI rt,CI c,CI p=D-1)//询问根到c的标记总和
		{
			if(!rt||!~p) return O[rt].V1;return O[rt].V1+Q1(O[rt].S[c>>p&1],c,p-1);
		}
		I void A2(int& rt,CI a,CI v,CI p=D-1)//单纯地插入a
		{
			if(!rt&&(rt=++Nt),O[rt].V2+=v,!~p) return;A2(O[rt].S[a>>p&1],a,v,p-1),PU(rt);
		}
		I int Q2(CI rt,CI c,CI d,CI p=D-1)//询问c,d对应的a的取值
		{
			if(!rt||!~p) return O[rt].V2;RI t=(c^d)>>p&1;return (d>>p&1?O[O[rt].S[t^1]].V2:0)+Q2(O[rt].S[t],c,d,p-1);//d这位为1可以不同,相同情况递归处理
		}
}T;
class SegmentTree
{
	private:
		#define PT CI l=1,CI r=n,CI rt=1
		#define LT l,mid,rt<<1
		#define RT mid+1,r,rt<<1|1
		int id[N+5];vector<Q> V[N<<2];vector<Q>::iterator it;
	public:
		I void K(CI L,CI R,Con Q& q,PT)//把询问扔到线段树上
		{
			if(L<=l&&r<=R) return (void)V[rt].push_back(q);RI mid=l+r>>1;
			L<=mid&&(K(L,R,q,LT),0),R>mid&&(K(L,R,q,RT),0);
		}
		I void Work(PT)//线段树分治,化区间询问为全局询问
		{
			RI i,Rt=0;for(T.Cl(),i=l;i<=r;++i) T.A2(Rt,a[id[i-l+1]=i],1);sort(id+1,id+r-l+2,cmp);//初始所有b[i]≥d[j]
			for(sort(V[rt].begin(),V[rt].end()),i=1,it=V[rt].begin();it!=V[rt].end();++it)//枚举所有询问
			{
				W(i<=r-l+1&&b[id[i]]<it->d) T.A1(Rt,a[id[i]],b[id[i]],1),T.A2(Rt,a[id[i++]],-1);//双指针
				ans[it->p]+=T.Q1(Rt,it->c)+T.Q2(Rt,it->c,it->d);//分两部分各自询问
			}
			if(l^r) {RI mid=l+r>>1;Work(LT),Work(RT);}//递归
		}
}S;
int main()
{
	RI i;for(read(n,Qt),i=1;i<=n;++i) read(a[i],b[i]);
	RI l,r,x,y;for(i=1;i<=Qt;++i) read(l,r,x,y),S.K(l,r,Q(i,x,y));S.Work();//把询问扔到线段树上离线
	for(i=1;i<=Qt;++i) writeln(ans[i]);return clear(),0;
}
posted @ 2021-04-05 11:45  TheLostWeak  阅读(56)  评论(0编辑  收藏  举报