zoukankan      html  css  js  c++  java
  • P3332

    好像是经典题(?),那就写一波题解罢。

    BIT 套动态开点线段树

    考虑查询,集合里的元素每个有两个指标:所在集合编号和自身权值,那么查询容易想到二分,二分的 chk 其实就是个二维数点。是动态的(实际上这个修改比动态加点强),二分的 chk 这玩意可以看作半在线,就暂时考虑在线动态维护二维数点了吧。

    那就考虑树套树,自然的想法是外层维护集合编号,内层维护权值。这样带来两个问题:修改操作相当于 (1 imes ?) 矩形增加,不会做,(? imes 1) 就会做了(就找到那一列在所在内层线段树上区修,之于外层 BIT 来说是单点修改);以及复杂度是 3log 的,感觉要 2log 才能过。

    其实让孩子搞基把内外层维护的 swap 一下(就跟 wjz 出的那个 taking a walk 一样的套路(那题题解鸽了快半年了,既然这样就永远鸽下去吧!))可以解决所有问题。前者不 solve 自 solve,后者的话,由于二分的是权值,而外层维护权值,所以可以在外层 BIT 倍增,把 log 平行化掉。

    这个方法啥都好,就是空间是 2log 的。

    code
    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    int lowbit(int x){return x&-x;}
    const int N=100010,LOG_N=18;
    int n,qu;
    int sz;
    struct node{signed lson,rson,lz;int cnt;}nd[N*200];
    #define lson(p) nd[p].lson
    #define rson(p) nd[p].rson
    #define lz(p) nd[p].lz
    #define cnt(p) nd[p].cnt
    int nwnd(){return nd[++sz]=node({0,0,0,0}),sz;}
    struct segtree{
    	int root;
    	void init(){root=nwnd();}
    	void sprdwn(int p,int l,int r){
    		if(lz(p)){
    			if(!lson(p))lson(p)=nwnd();if(!rson(p))rson(p)=nwnd();
    			int mid=l+r>>1;
    			cnt(lson(p))+=1ll*lz(p)*(mid-l+1),lz(lson(p))+=lz(p);
    			cnt(rson(p))+=1ll*lz(p)*(r-mid),lz(rson(p))+=lz(p);
    			lz(p)=0;
    		}
    	}
    	void sprup(int p){cnt(p)=cnt(lson(p))+cnt(rson(p));}
    	void add(int l,int r,int p=-1,int tl=1,int tr=n){~p||(p=root);
    		if(l<=tl&&r>=tr)return cnt(p)+=tr-tl+1,lz(p)++,void();
    		sprdwn(p,tl,tr);
    		int mid=tl+tr>>1;
    		if(l<=mid){
    			if(!lson(p))lson(p)=nwnd();
    			add(l,r,lson(p),tl,mid);
    		}
    		if(r>mid){
    			if(!rson(p))rson(p)=nwnd();
    			add(l,r,rson(p),mid+1,tr);
    		}
    		sprup(p);
    	}
    	int _cnt(int l,int r,int p=-1,int tl=1,int tr=n){~p||(p=root);
    		if(l<=tl&&r>=tr)return cnt(p);
    		sprdwn(p,tl,tr);
    		int mid=tl+tr>>1,res=0;
    		if(l<=mid)res+=_cnt(l,r,lson(p),tl,mid);
    		if(r>mid)res+=_cnt(l,r,rson(p),mid+1,tr);
    		return res;
    	}
    };
    struct bitree{
    	segtree cnt[N];
    	void init(){
    		for(int i=1;i<=2*n+1;i++)cnt[i].init();
    	}
    	void add(int x,int l,int r){
    		while(x<=2*n+1)cnt[x].add(l,r),x+=lowbit(x);
    	}
    	int fd(int l,int r,int x){
    		int res=0,_cnt=0;
    		for(int i=LOG_N-1;~i;i--)if(res+(1<<i)<=2*n+1&&_cnt+cnt[res+(1<<i)]._cnt(l,r)<x)
    			res+=1<<i,_cnt+=cnt[res]._cnt(l,r);
    		return res+1;
    	}
    }bit;
    signed main(){
    //	cout<<sizeof(nd[0]);
    	cin>>n>>qu;
    	bit.init();
    	while(qu--){
    		int tp,l,r,x;
    		scanf("%lld%lld%lld%lld",&tp,&l,&r,&x);
    		if(tp==1)bit.add(-x+n+1,l,r);
    		else printf("%lld
    ",-(bit.fd(l,r,x)-n-1));
    	}
    	return 0;
    }
    

    整体二分

    由于允许离线,考虑整体二分。

    二分区间 ([l,r]) 的时候,我们 chk 要看的事情就是对每个询问 (([l_0,r_0],x))([l_0,r_0]) 内小于等于 (mid) 的个数是否小于等于 (x)。我们需要动态求这玩意,这其实就很简单了:按顺序扫所有的 query,如果是修改 (([l_0,r_0],x)) 并且 (xleq mid) 那么它有贡献,区间 ([l_0,r_0]) 加;如果是查询那就线段树里面查就可以了。

    但目前有个问题:(x) 特别小的修改有可能在很多很多整体二分的子问题里面贡献非零,这样它一路走的路径就不是一条直链!这里涉及二分的一个常用技巧:当前层如果某个询问 (([l_0,r_0],x)) 查出来个数是 (y)(x>y),那 duck 令 (xgets x-y),这样以后就不用考虑前面的修改的贡献了。那这样每层处理完后,不光查询会被分到左右两部分中的一部分(这个在任何整体二分中都恒成立),修改也是被分到恰好(或者说最多?)一部分,没被分到就是贡献必定为 (0)

    这样一来每个整体二分的子问题就是关于被分到的 query 个数(查询 + 修改)的 1log,总复杂度就是 2log。注意每次不能给线段树暴力 init(那样复杂度就加上总值域,就被破坏了),需要每次处理完加上负值抵消掉。

    code
    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    #define pb push_back
    const int N=100010;
    int n,qu;
    struct query{int tp,l,r,x,id;}qry[N];
    struct segtree{
    	struct node{int cnt,lz;}nd[N<<2];
    	#define cnt(p) nd[p].cnt
    	#define lz(p) nd[p].lz
    	void init(){memset(nd,0,sizeof(nd));}
    	void sprdwn(int p,int l,int r){
    		if(lz(p)){
    			int mid=l+r>>1;
    			cnt(p<<1)+=1ll*lz(p)*(mid-l+1),lz(p<<1)+=lz(p);
    			cnt(p<<1|1)+=1ll*lz(p)*(r-mid),lz(p<<1|1)+=lz(p);
    			lz(p)=0;
    		}
    	}
    	void sprup(int p){cnt(p)=cnt(p<<1)+cnt(p<<1|1);}
    	void add(int l,int r,int v,int p=1,int tl=1,int tr=n){
    		if(l<=tl&&r>=tr)return cnt(p)+=v*(tr-tl+1),lz(p)+=v,void();
    		sprdwn(p,tl,tr);
    		int mid=tl+tr>>1;
    		if(l<=mid)add(l,r,v,p<<1,tl,mid);
    		if(r>mid)add(l,r,v,p<<1|1,mid+1,tr);
    		sprup(p);
    	}
    	int _cnt(int l,int r,int p=1,int tl=1,int tr=n){
    		if(l<=tl&&r>=tr)return cnt(p);
    		sprdwn(p,tl,tr);
    		int mid=tl+tr>>1,res=0;
    		if(l<=mid)res+=_cnt(l,r,p<<1,tl,mid);
    		if(r>mid)res+=_cnt(l,r,p<<1|1,mid+1,tr);
    		return res;
    	}
    }segt;
    int ans[N];
    void solve(int l,int r,vector<query> v){
    	if(l==r){
    		for(int i=0;i<v.size();i++){
    			int tp=v[i].tp,id=v[i].id;
    			if(tp==2)ans[id]=l;
    		}
    		return;
    	}
    	int mid=l+r>>1;
    	vector<query> lft,rit;
    	for(int i=0;i<v.size();i++){
    		int tp=v[i].tp,l=v[i].l,r=v[i].r,x=v[i].x;
    		if(tp==1){
    			if(x<=mid)segt.add(l,r,1),lft.pb(v[i]);
    			else rit.pb(v[i]);
    		}
    		else{
    			int fd=segt._cnt(l,r);
    			if(x<=fd)lft.pb(v[i]);
    			else v[i].x-=fd,rit.pb(v[i]);
    		}
    	}
    	for(int i=0;i<v.size();i++){
    		int tp=v[i].tp,l=v[i].l,r=v[i].r,x=v[i].x,id=v[i].id;
    		if(tp==1&&x<=mid)segt.add(l,r,-1);
    	}
    	solve(l,mid,lft),solve(mid+1,r,rit);
    }
    signed main(){
    	cin>>n>>qu;
    	for(int i=1;i<=qu;i++)scanf("%lld%lld%lld%lld",&qry[i].tp,&qry[i].l,&qry[i].r,&qry[i].x),qry[i].id=i;
    	vector<query> v;
    	for(int i=1;i<=qu;i++)qry[i].tp==1&&(qry[i].x*=-1),v.pb(qry[i]);
    	segt.init();
    	solve(-n,n,v);
    	for(int i=1;i<=qu;i++)if(qry[i].tp==2)printf("%lld
    ",-ans[i]);
    	return 0;
    }
    

    二进制分组

    上面的上面那个在线动态二维数点考虑一波二进制分组 + 主席树。如果主席树的历史版本编号是权值的话,那照样是只能 3log 的;如果颠倒过来的话,依然可以在一棵虚空中的线段树(其实就是只有位置信息)上二分,每次把 2 倍 log 棵线段树的该位置加(减)起来,就相当于 log 个静态区间第 k 大并起来做一样,是可以做到 2log 的。但好像比较难写,就不写了 2333。

    珍爱生命,远离抄袭!
  • 相关阅读:
    继承关系·
    对象第复制operator=
    关于类拷贝造函数
    静态数据成员与静态成员函数
    linux新内核的时钟机制代码
    RTC系统
    Android关机闹钟实现
    更改printk打印级别
    vncserver 配置全屏显示
    vnc里鼠标拖动终端就会产生ctrl+c终端
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/solution-p3332.html
Copyright © 2011-2022 走看看