zoukankan      html  css  js  c++  java
  • 【洛谷7470】[NOI Online 2021 提高组] 岛屿探险(线段树分治+Trie树)

    点此看题面

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

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

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

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

    线段树分治

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

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

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

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

    (Trie)树乱搞

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

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

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

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

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

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

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

    第二部分((a_ioplus 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('
    ');}
    }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;
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    字符串匹配常见算法(BF,RK,KMP,BM,Sunday)
    JSP基本语法总结【1】(jsp工作原理,脚本元素,指令元素,动作元素)
    JUnit【1】断言用法之assertEquals/True/False/ArrayEquals
    软件测试基础配置
    前端入门20-JavaScript进阶之异步回调的执行时机
    前端入门19-JavaScript进阶之闭包
    前端入门18-JavaScript进阶之作用域链
    前端入门17-JavaScript进阶之作用域
    前端入门16-JavaScript进阶之EC和VO
    前端入门15-JavaScript进阶之原型链
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu7470.html
Copyright © 2011-2022 走看看