(笔记)树套树

树状数组套线段树

P3380 【模板】树套树

树状数组能够维护的区间信息具有可差分性,显然本题的查询可以通过若干个二分查询解决,写一个值域线段树,排名为 \(k\) 在线段树上二分,\(k\) 的排名直接查询树状数组 \([l,r](tre[r]-tre[l-1])\) 上线段树的 \([1,k-1]+1\) 即可。前驱后继通过知道排名求得。需要注意这样空间还是挺大的,线段树动态开点后每次加入一就有 \(O(\log n\log V)\)(V 为值域)个节点被创建,需要注意数据范围。时间复杂度约是 \(O((n+m)\log n\log V)\) 的,其时间不容小觑,甚至空间复杂度也差不多,只能说挺暴力的。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
#define ls t[p].lc
#define rs t[p].rc
#define mid ((l+r)>>1)
int lsh[N*2],len,a[N];
struct Node{
	int lc,rc,sum;
}t[N*100];
int n,m;
struct Q{
	int opt,l,r,k,pos;
}q[N];
int cntp;
int root[N];
void pushup(int p){t[p].sum=t[ls].sum+t[rs].sum;}
void update(int &p,int l,int r,int pos,int val){
	if(!p)p=++cntp;
	if(l==r){t[p].sum+=val;return ;}
	if(pos<=mid)update(ls,l,mid,pos,val);
	else update(rs,mid+1,r,pos,val);
	pushup(p);
}
int query(int p,int l,int r,int L,int R){
	if(L>R)return 0;
	if(!p)return 0;
	if(L<=l&&r<=R)return t[p].sum;
	int res=0;
	if(L<=mid)res+=query(ls,l,mid,L,R);
	if(R>mid)res+=query(rs,mid+1,r,L,R);
	return res;
}
int lowbit(int x){return x&-x;}
void ins(int p,int x,int v){
	for(int i=p;i<=n;i+=lowbit(i))
		update(root[i],1,len,x,v);
}
int que(int p,int l,int r){
	int res=0;
	for(int i=p;i;i-=lowbit(i))
		res+=query(root[i],1,len,l,r);
	return res;
}
int ql[N],qr[N];
int cntl,cntr;
int qsum(int l,int r,int k){
	if(l==r)return l;
	int sum=0;
	for(int i=1;i<=cntl;i++)sum-=query(ql[i],1,len,l,mid);
	for(int i=1;i<=cntr;i++)sum+=query(qr[i],1,len,l,mid);
	if(sum>=k)return qsum(l,mid,k);
	return qsum(mid+1,r,k-sum);
}
int qrank(int k,int l,int r){
	l--;
	cntl=0,cntr=0;
	for(int i=l;i;i-=lowbit(i))
		ql[++cntl]=root[i];
	for(int i=r;i;i-=lowbit(i))
		qr[++cntr]=root[i];
	return qsum(1,len,k);
}
inline int getrnk(int k,int l,int r){return que(r,1,k-1)-que(l-1,1,k-1)+1; }
#undef ls
#undef rs
#undef mid
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>a[i],lsh[++len]=a[i];
	for(int i=1;i<=m;i++){
		int opt,l,r,k,pos;
		cin>>opt;
		if(opt==3){
			cin>>pos>>k;
			lsh[++len]=k;
			q[i]=(Q){opt,0,0,k,pos};
		}
		else {
			cin>>l>>r>>k;
			if(opt!=2)lsh[++len]=k;
			q[i]=(Q){opt,l,r,k,0};
		}
	}
	sort(lsh+1,lsh+1+len);
	len=unique(lsh+1,lsh+1+len)-(lsh+1);
	for(int i=1;i<=n;i++){
		a[i]=lower_bound(lsh+1,lsh+1+len,a[i])-lsh;
		ins(i,a[i],1);
	}
	for(int i=1;i<=m;i++)
		if(q[i].opt!=2)q[i].k=lower_bound(lsh+1,lsh+1+len,q[i].k)-lsh;
	for(int i=1;i<=m;i++){
		int opt,l,r,k,pos;
		opt=q[i].opt,k=q[i].k;
		l=q[i].l,r=q[i].r,pos=q[i].pos;
		if(opt==1)
			cout<<getrnk(k,l,r)<<'\n';
		else if(opt==2)
			cout<<lsh[qrank(k,l,r)]<<'\n';
		else if(opt==3){
			ins(pos,a[pos],-1);
			a[pos]=k;
			ins(pos,a[pos],1);
		}
		else if(opt==4){
			int c=getrnk(k,l,r);
			if(c==1)cout<<"-2147483647"<<'\n';
			else cout<<lsh[qrank(c-1,l,r)]<<'\n';
		}
		else if(opt!=3){
			if(k==len){cout<<"2147483647"<<'\n';break;}
			int c=getrnk(k+1,l,r);
			if(c==r-l+2)cout<<"2147483647"<<'\n';
			else cout<<lsh[qrank(c,l,r)]<<'\n';
		}
	}
	return 0;
}

二维线段树

每个线段树节点内再嵌套一颗线段树,理论上每次更新需要合并外层线段树左右儿子的内层线段树(只合并更改了的部分,否则无法承受),可使用标记永久化。这玩意常数挺大的,单次修改达到 \(O(\log n\log m)\),而且如果两维都是可差分信息的话跑起来比二维树状数组不知道要慢多少。

P2086 [NOI2012] 魔幻棋盘

但是它既然出现了,说明两维都是不可差分信息。它也仅仅适用于那些 \(n\times m\) 在接受范围内的数据,像 \(n,m\le 10^5\) 是没戏了。先挖掘一个 \(\gcd\) 的性质,\(\gcd(a_1,a_2,\dotsb,a_n)=\gcd(a_i,a_2-a_1,a_3-a_2,\dotsb,a_n-a_{n-1})\),其中 \(i\in[1,n]\) 取任意整数,这样就把一个长度为 \(n\) 的序列的 \(\gcd\) 转化为了另一个长度为 \(n\) 的序列的 \(\gcd\),而且很方便区间修改。

根据题解 P2086 【[NOI2012]魔幻棋盘】的思路我们可以把每个询问矩形进行行列分别差分得到新矩形,行差分的话行上第一个就是一个必然存在的数,列差分的第一个也是一个列上必然存在的数,考虑到询问区间一定过 \((X,Y)\),把第 \(X\) 行和第 \(Y\) 列都拎出来即可,然后就是朴素差分。假设询问矩形左上角为 \(a_{c,d}\),考虑 \(3\times 3\) 的情况。

\[\begin{bmatrix} a_{c,d} & a_{c,d+1} & a_{c,d+2}\\ a_{c+1,d} & a_{c+1,d+1} & a_{c+1,d+2}\\ a_{c+2,d} & a_{c+2,d+1} & a_{c+2,d+2} \end{bmatrix} \]

行差分后:

\[\begin{bmatrix} a_{c,Y} & a_{c,d+1}-a_{c,d} & a_{c,d+2}-a_{c,d+1}\\ a_{c+1,Y} & a_{c+1,d+1}-a_{c+1,d} & a_{c+1,d+2}-a_{c+1,d+1}\\ a_{c+2,Y} & a_{c+2,d+1}-a_{c+2,d} & a_{c+2,d+2}-a_{c+2,d+1} \end{bmatrix} \]

列差分后:

\[\begin{bmatrix} a_{X,Y} & a_{X,d+1}-a_{X,d} & a_{X,d+2}-a_{X,d+1}\\ a_{c+1,Y}-a_{c,Y} & a_{c+1,d+1}-a_{c+1,d}-a_{c,d+1}+a_{c,d} & a_{c+1,d+2}-a_{c+1,d+1}-a_{c,d+2}+a_{c,d+1}\\ a_{c+2,Y}-a_{c+1,Y} & a_{c+2,d+1}-a_{c+2,d}-a_{c+1,d+1}+a_{c+1,d} & a_{c+2,d+2}-a_{c+2,d+1}-a_{c+1,d+2}+a_{c+1,d+1} \end{bmatrix} \]

我们发现,对于一个查询矩阵可以分为四个部分,一个部分是 \((1,1)\),还有去掉 \((1,1)\) 的第一行和第一列,还有右下角的矩阵。注意到这些都是差分信息,直接上数据结构分别维护即可。 \(a_{X,Y}\) 可单独维护,然后分别用两个一维线段树维护行列,用一个二维线段树维护右下角矩阵即可,这里使用树套树完成了本题维护,两层都是动态开点。

线段树套线段树单点改矩阵查是好维护的,但是需要注意的是由于不再维护可差分信息,需要想清楚在外层线段树非叶子节点内嵌套的线段树的意义,显然我们希望它是左右儿子的合并,我一开始写了和单点加区间查一样的线段树,没有写 Pushup 合并,这是错误的,并且调了我一晚上(?),具体来说,外层线段树非叶子节点 \(u\) 中内层线段树节点 \(u_v\)\(val\)(记录内层区间 \(\gcd\))应该是 \(ls_v,rs_v\)\(val\)\(\gcd\)。单点加区间查之所以能直接一路修改下来是因为它的内层线段树合并信息是可差分的,否则我们应该根据修改(只有 \(O(\log m)\) 次)进行 Pushup

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=5e5+5;
int n,m,X,Y,T;
int rtx,rty,ncnt,rt,dcnt;
vector<LL>a[N];
LL gcd(LL a,LL b){
  return b?gcd(b,a%b):abs(a);
}
#define ls t[p].lc
#define rs t[p].rc
#define mid ((l+r)>>1)
struct Node{int lc,rc;LL res;}t[N<<5];
struct node{int lc,rc,id;}D[N<<2];
void pushup(int p){t[p].res=gcd(t[ls].res,t[rs].res);}
void modify(int &p,int l,int r,int pos,LL v){
  if(!p)p=++ncnt;
  if(l==r){t[p].res+=v;return ;}
  if(pos<=mid)modify(ls,l,mid,pos,v);
  else modify(rs,mid+1,r,pos,v);
  pushup(p);
}
LL query(int p,int l,int r,int L,int R){
  if(!p)return 0;
  if(L<=l&&r<=R)return t[p].res;
  if(L<=mid&&R>mid)return gcd(query(ls,l,mid,L,R),query(rs,mid+1,r,L,R));
  if(L<=mid)return query(ls,l,mid,L,R);
  return query(rs,mid+1,r,L,R);
}
#undef ls
#undef rs
#define ls D[p].lc
#define rs D[p].rc
void Pushup(int &p,int l,int r,int x,int y,int pos){
  if(!p)p=++ncnt;
  t[p].res=gcd(t[x].res,t[y].res);
  if(l==r)return ;
  if(pos<=mid)Pushup(t[p].lc,l,mid,t[x].lc,t[y].lc,pos);
  else Pushup(t[p].rc,mid+1,r,t[x].rc,t[y].rc,pos);
}
void Modify(int &p,int l,int r,int x,int y,LL v){
  if(!p)p=++dcnt;
  if(l==r){modify(D[p].id,1,m,y,v);return ;}
  if(x<=mid)Modify(ls,l,mid,x,y,v);
  else Modify(rs,mid+1,r,x,y,v);
  Pushup(D[p].id,1,m,D[ls].id,D[rs].id,y);
}
LL Query(int p,int l,int r,int L,int R,int x,int y){
  if(!p)return 0;
  if(L<=l&&r<=R)return query(D[p].id,1,m,x,y);
  if(L<=mid&&R>mid)return gcd(Query(ls,l,mid,L,R,x,y),Query(rs,mid+1,r,L,R,x,y));
  if(L<=mid)return Query(ls,l,mid,L,R,x,y);
  return Query(rs,mid+1,r,L,R,x,y);
}
#undef ls
#undef rs
#undef mid
LL mem;
int main(){
  scanf("%d%d%d%d%d",&n,&m,&X,&Y,&T);
  for(int j=0;j<=m;j++)
    a[0].push_back(0);
  for(int i=1;i<=n;i++){
    a[i].push_back(0);
    for(int j=1;j<=m;j++){
      a[i].push_back(0);
      scanf("%lld",&a[i][j]);
    }
  }
  for(int i=1;i<=n;i++){
    LL my=a[i][Y]-a[i-1][Y];
    modify(rtx,1,n,i,my);
  }
  for(int i=1;i<=m;i++){
    LL my=a[X][i]-a[X][i-1];
    modify(rty,1,m,i,my);
  }
  mem=a[X][Y];
  for(int i=n;i>=1;i--){
    for(int j=m;j>=1;j--){
      a[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1];
      Modify(rt,1,n,i,j,a[i][j]);
    }
  }
  while(T--){
    int op,xa,ya,xb,yb;
    scanf("%d%d%d%d%d",&op,&xa,&ya,&xb,&yb);
    if(op){
      LL c;scanf("%lld",&c);
      if(xa<=X&&X<=xb&&ya<=Y&&Y<=yb)
        mem+=c;
      Modify(rt,1,n,xa,ya,c);
      if(xb+1<=n)Modify(rt,1,n,xb+1,ya,-c);
      if(yb+1<=m)Modify(rt,1,n,xa,yb+1,-c);
      if(xb+1<=n&&yb+1<=m)Modify(rt,1,n,xb+1,yb+1,c);
      if(ya<=Y&&Y<=yb){
        modify(rtx,1,n,xa,c);
        if(xb+1<=n)modify(rtx,1,n,xb+1,-c);
      }
      if(xa<=X&&X<=xb){
        modify(rty,1,m,ya,c);
        if(yb+1<=m)modify(rty,1,m,yb+1,-c);
      }
    }
    else {
      LL ans=abs(mem);
      xa=X-xa,xb=X+xb,ya=Y-ya,yb=Y+yb;
      if(xa<xb&&ya<yb)
        ans=gcd(ans,Query(rt,1,n,xa+1,xb,ya+1,yb));
      if(xa<xb)ans=gcd(query(rtx,1,n,xa+1,xb),ans);
      if(ya<yb)ans=gcd(query(rty,1,m,ya+1,yb),ans);
      printf("%lld\n",ans);
    }
  }
  return 0;
}
posted @ 2025-08-04 08:31  TBSF_0207  阅读(19)  评论(0)    收藏  举报