「APIO2019」路灯 (K-D Tree / 树套树 / CDQ + 树状数组)

「APIO2019」路灯 (K-D Tree / 树套树 / CDQ + 树状数组)

首先想到一个简单的问题转化

对于一个询问,联通的时间是若干连续的区间\([L_i,R_i]\)

所有的\(L_i,R_i+1\)都是关键点,即由不连通变为联通的时间 和 由联通变为不连通的时间

把答案转化为\(\sum R_i+1-L_i\)即可

问题转化为对于当前的操作,找到它是那些询问的关键点

如果是合并操作,被合并的两个区间之间变得联通

如果是分裂操作,裂开的两个区间之间不再联通

可以用set维护上述区间,发现每次被更新的值都是一个二维区间

算上时间这一维,问题转化为一个类 三维偏序问题,但是题限制了内存

Part1 K-D Tree

限制了内存,很容易想到直接K-D Tree,实际运行也比较优秀

注意可以把要询问的点拿出来建出K-D Tree,每次区间修改即可

时间复杂度\(O(n\sqrt n)\),空间复杂度\(O(n)\)

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define pb push_back
typedef pair <int,int> Pii;
#define mp make_pair
void cmin(int &a,int b){ ((a>b)&&(a=b)); }
void cmax(int &a,int b){ ((a<b)&&(a=b)); }

char IO;
int rd(){
	int s=0;
	while(!isdigit(IO=getchar()));
	do s=(s<<1)+(s<<3)+(IO^'0');
	while(isdigit(IO=getchar()));
	return s;
}
const int N=3e5+10,INF=1e9;

int n,m,rt,col[N],opt[N],a[N],b[N];
char str[N];
set <pair <int,int> > st,tmp;
struct Node{ int x,y; } A[N];
int cmp1(Node a,Node b){ return mp(a.x,a.y)<mp(b.x,b.y); }
int cmp2(Node a,Node b){ return mp(a.y,a.x)<mp(b.y,b.x); }
int ch[N][2],lx[N],rx[N],ly[N],ry[N],s[N],t[N];
int Build(int l,int r,int d=0) { 
	if(l>r) return 0;
	int u=(l+r)>>1;
    nth_element(A+l,A+u,A+r+1,d?cmp2:cmp1);
	ch[u][0]=Build(l,u-1,d^1),ch[u][1]=Build(u+1,r,d^1);
	lx[u]=rx[u]=A[u].x,ly[u]=ry[u]=A[u].y;
	for(int i:ch[u]) if(i) cmin(lx[u],lx[i]),cmin(ly[u],ly[i]),cmax(rx[u],rx[i]),cmax(ry[u],ry[i]);
	return u;
}

void Upd(int x1,int x2,int y1,int y2,int u,int x) {
	if(!u || x1>rx[u] || x2<lx[u] || y1>ry[u] || y2<ly[u]) return;
	if(x1<=lx[u] && rx[u]<=x2 && y1<=ly[u] && ry[u]<=y2) return void(s[u]+=x);
	if(x1<=A[u].x && A[u].x<=x2 && y1<=A[u].y && A[u].y<=y2) t[u]+=x;
	for(int i:ch[u]) Upd(x1,x2,y1,y2,i,x);
}
int Que(Node x,int u,int d=0) {
	if(A[u].x==x.x && A[u].y==x.y) return s[u]+t[u];
	int y=ch[u][!(d?cmp2(x,A[u]):cmp1(x,A[u]))];
	return Que(x,y,d^1)+s[u];
}

int main(){
	n=rd(),m=rd();
	scanf("%s",str+1);
	rep(i,1,n) col[i]=str[i]-'0';
	rep(i,1,n+1) {
		int r=i;
		while(col[r]) r++;
		st.insert(mp(i,r));
		i=r;
	}
	rep(i,1,m) {
		scanf("%s",str+1);
		if(str[1]=='t') opt[i]=1,a[i]=rd();
		else opt[i]=2,a[i]=rd(),b[i]=rd(),tmp.insert(mp(a[i],b[i]));
	}
	n=0;
	for(auto it:tmp) A[++n]=(Node){it.first,it.second};
	rt=Build(1,n);
	rep(i,1,m) {
		if(opt[i]==1) {
			int x=a[i];
			if(col[x]) {
				auto it=st.upper_bound(mp(x,INF)); it--;
				int l=it->first,r=it->second;
				st.erase(it);
				st.insert(mp(l,x)),st.insert(mp(x+1,r));
				Upd(l,x,x+1,r,rt,i);
			} else {
				auto it=st.upper_bound(mp(x,INF)),tmp=it; it--;
				int l=it->first,r=tmp->second; 
				st.erase(it),st.erase(tmp);
				st.insert(mp(l,r)),Upd(l,x,x+1,r,rt,-i);
			}
			col[x]^=1;
		} else {
			int ans=Que((Node){a[i],b[i]},rt);
			auto it=st.upper_bound(mp(a[i],INF)); it--;
			if(it->second>=b[i]) ans+=i;
			printf("%d\n",ans);
		}
	}
}

Part2 树套树(没有代码)

由于已知查询的节点,树套树的内存可以优化到\(O(n\log n)\)

把要询问的点拉出来,每次询问在第二维中有\(\log n\)次单点查询,所以需要被查询的位置一共只有\(n\log n\)

把这些会被查询的位置拿出来建成\(n\)棵静态的线段树,更新就直接在这些静态的线段树上区间更新即可

时间复杂度\(O(n\log ^2 n)\),空间复杂度\(O(n\log n)\)

Part3 CDQ+树状数组

是常规写法,不会被限制内存

CDQ一维,排序一维,树状数组一维,参见三维偏序

posted @ 2020-07-26 14:02  chasedeath  阅读(202)  评论(0编辑  收藏  举报