[浅谈] 二维数据结构——树套树
回忆一下树状数组的区间修改与查询操作,后面有用。
维护差分数组
则:
二维树状数组。
同上面那题一维的一样来推式子。
先推怎么差分,这个画个图推,想办法让其差分前缀和等于原数。然后得到下图:
所以差分的方法为:
可以修改了,对吧。
然后怎么查询:
(发现对于一个
(然后我推了半天,发现画图能很好地推出来)
上面的值都能维护了啊。
那么二维树状数组就结束了。
Code
#include<bits/stdc++.h>
// #define int long long
using namespace std;
const int N=3010;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
int n,m,de[N][N],dei[N][N],dej[N][N],deij[N][N];
void ude(int x,int y,int k){
for(int i=x;i<=n;i+=i&(-i))
for(int j=y;j<=m;j+=j&(-j))
de[i][j]+=k;
return;
}
void udei(int x,int y,int k){
for(int i=x;i<=n;i+=i&(-i))
for(int j=y;j<=m;j+=j&(-j))
dei[i][j]+=k;
return;
}
void udej(int x,int y,int k){
for(int i=x;i<=n;i+=i&(-i))
for(int j=y;j<=m;j+=j&(-j))
dej[i][j]+=k;
return;
}
void udeij(int x,int y,int k){
for(int i=x;i<=n;i+=i&(-i))
for(int j=y;j<=m;j+=j&(-j))
deij[i][j]+=k;
return;
}
int query(int it[][N],int x,int y){
int res=0;
for(int i=x;i>0;i-=i&(-i))
for(int j=y;j>0;j-=j&(-j))
res+=it[i][j];
return res;
}
int getsum(int x,int y){
return query(de,x,y)*x*y-query(dei,x,y)*y-query(dej,x,y)*x+query(deij,x,y);
}
char Startrunning,opt;
signed main(){cin>>Startrunning;
n=1+read(),m=1+read();
while(scanf("%s",&opt)!=EOF){
if(opt=='L'){
int a=1+read(),b=1+read(),c=1+read(),d=1+read(),k=read();
ude(a,b,k);
ude(c+1,d+1,k);
ude(a,d+1,-k);
ude(c+1,b,-k);
udei(a,b,k*(a-1));
udei(c+1,d+1,k*c);
udei(a,d+1,-k*(a-1));
udei(c+1,b,-k*c);
udej(a,b,k*(b-1));
udej(c+1,d+1,k*d);
udej(a,d+1,-k*d);
udej(c+1,b,-k*(b-1));
udeij(a,b,k*(a-1)*(b-1));
udeij(c+1,d+1,k*c*d);
udeij(a,d+1,-k*(a-1)*d);
udeij(c+1,b,-k*(b-1)*c);
}
else{
int a=1+read(),b=1+read(),c=1+read(),d=1+read();
printf("%d\n",getsum(c,d)-getsum(a-1,d)-getsum(c,b-1)+getsum(a-1,b-1));
}
}
return 0;
}
即在修改时一路更新路过区间的值,在覆盖是打上永久标记。然后查询时一路带上标记。
它能够大量减少代码量但使用时要思考清楚。
题意是给定一个二维平面,每个点有一个权值,有两种操作,查询矩阵最大值,将矩阵赋值为一个更大的数。
考虑一维怎么做:一颗线段树,使用标记永久化。
考虑二维,对每个上面那棵树上的每个点维护一颗线段树,维护第二维的值,但是对不同的变量要开不同的树维护。
Code
#include<bits/stdc++.h>
using namespace std;
const int N=4010;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
int D,S,n;
struct SegmentTreeY{
int maxn[N],tag[N];
void update(int u,int l,int r,int L,int R,int x){
maxn[u]=max(x,maxn[u]);//?
if(L<=l && r<=R){tag[u]=max(tag[u],x);return;}//?//?
int mid=(l+r)>>1;
if(L<=mid)update(u<<1,l,mid,L,R,x);
if(R>mid) update(u<<1|1,mid+1,r,L,R,x);
return;
}
int query(int u,int l,int r,int L,int R){
if(L<=l && r<=R){return maxn[u];}//?
int res=tag[u],mid=(l+r)>>1;
if(L<=mid)res=max(res,query(u<<1,l,mid,L,R));
if(R>mid) res=max(res,query(u<<1|1,mid+1,r,L,R));
return res;
}
};
struct SegmentTreeX{
SegmentTreeY maxn[N],tag[N];
void update(int u,int l,int r,int L,int R,int yl,int yr,int x){
maxn[u].update(1,1,S,yl,yr,x);
if(L<=l && r<=R){tag[u].update(1,1,S,yl,yr,x);return;}//?
int mid=(l+r)>>1;
if(L<=mid)update(u<<1,l,mid,L,R,yl,yr,x);
if(R>mid) update(u<<1|1,mid+1,r,L,R,yl,yr,x);
return;
}
int query(int u,int l,int r,int L,int R,int yl,int yr){
if(L<=l && r<=R){return maxn[u].query(1,1,S,yl,yr);}//?
int res=tag[u].query(1,1,S,yl,yr),mid=(l+r)>>1;
if(L<=mid)res=max(res,query(u<<1,l,mid,L,R,yl,yr));
if(R>mid) res=max(res,query(u<<1|1,mid+1,r,L,R,yl,yr));
return res;
}
}E1;
int main(){
D=read(),S=read(),n=read();
for(int o=1;o<=n;o++){
int d=read(),s=read(),w=read(),x=read()+1,y=read()+1;
E1.update(1,1,D,x,x+d-1,y,y+s-1,E1.query(1,1,D,x,x+d-1,y,y+s-1)+w);
}
printf("%d\n",E1.query(1,1,D,1,D,1,S));
return 0;
}
显然,二维树状数组的局限性大,数据大就开不了二维数组;二维线段树的局限性也大,常数大,效率低。
所以我们可以用树状数组套线段树完美地解决这个问题。
具体查询的时候,先把涉及的线段树根节点全部存入一个数组或
内层用权值线段树,方便求出第
Ancient Code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+514;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){
if(c=='-')f=-1;
c=getchar();
}
while(c>='0' && c<='9'){
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x*f;
}
int cnt,n,m,tot,len,Real[N*2],root[N],rcd_u[N],rcd_v[N],len_u,len_v;
struct st_lsh{
int num,x,flag;
}a[N*2];
struct st_ask{
int opt,l,r,k;
}q[N];
struct st_tree{
int lson,rson,sum;
}e[N*400];
bool cmp1(st_lsh t1,st_lsh t2){
return t1.num<t2.num;
}
bool cmp2(st_lsh t1,st_lsh t2){
if(t1.flag!=t2.flag)return t1.flag<t2.flag;
return t1.x<t2.x;
}
int lowbit(int u){
return u&(-u);
}
void lsh(){
int last=-1;
sort(a+1,a+len+1,cmp1);
for(int i=1;i<=len;i++){
if(a[i].num!=last)tot++;
last=a[i].num;
Real[tot]=a[i].num;
a[i].num=tot;
}
sort(a+1,a+len+1,cmp2);
for(int i=n+1;i<=len;i++)
q[a[i].x].r=a[i].num;
return;
}
void pushup(int u){
e[u].sum=e[e[u].lson].sum+e[e[u].rson].sum;
return;
}
void update(int &u,int l,int r,int val,int add){
if(!u)u=++cnt;
if(l==r){
e[u].sum+=add;
return;
}
int mid=(l+r)>>1;
if(val<=mid)update(e[u].lson,l,mid,val,add);
else update(e[u].rson,mid+1,r,val,add);
pushup(u);
return;
}
void prepare_update(int now,int val,int add){
for(int i=now;i<=n;i+=lowbit(i))update(root[i],1,tot,val,add);
return;
}
int query(int l,int r,int k){
if(l==r)return l;
int mid=(l+r)>>1;
int del=0;
for(int i=1;i<=len_u;i++)del+=e[e[rcd_u[i]].lson].sum;
for(int i=1;i<=len_v;i++)del-=e[e[rcd_v[i]].lson].sum;
if(del>=k){
for(int i=1;i<=len_u;i++)rcd_u[i]=e[rcd_u[i]].lson;
for(int i=1;i<=len_v;i++)rcd_v[i]=e[rcd_v[i]].lson;
return query(l,mid,k);
}
else{
for(int i=1;i<=len_u;i++)rcd_u[i]=e[rcd_u[i]].rson;
for(int i=1;i<=len_v;i++)rcd_v[i]=e[rcd_v[i]].rson;
return query(mid+1,r,k-del);
}
}
int prepare_query(int v,int u,int k){
len_u=len_v=0;
for(int i=u;i;i-=lowbit(i))rcd_u[++len_u]=root[i];
for(int i=v;i;i-=lowbit(i))rcd_v[++len_v]=root[i];
return query(1,tot,k);
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)
a[++len].num=read(),a[len].x=i,a[len].flag=1;
for(int i=1;i<=m;i++){
char tmp;
cin>>tmp;
if(tmp=='Q'){
q[i].opt=1;
q[i].l=read(),q[i].r=read(),q[i].k=read();
}
else {
q[i].opt=2;
q[i].l=read(),q[i].r=read();
a[++len].flag=2,a[len].num=q[i].r,a[len].x=i;
}
}
lsh();
for(int i=1;i<=n;i++)prepare_update(i,a[i].num,1);
for(int i=1;i<=m;i++){
if(q[i].opt==1)printf("%d\n",Real[prepare_query(q[i].l-1,q[i].r,q[i].k)]);
else{
prepare_update(q[i].l,a[q[i].l].num,-1);
prepare_update(q[i].l,q[i].r,1);
a[q[i].l].num=q[i].r;
}
}
return 0;
}
二维偏序怎么做?按第一维排序,第二维用树状数组求前缀和做。
三维偏序怎么做?按第一维排序,第二,三维用二维前缀和做。
怎么求二维前缀和?使用二维树状数组似乎空间开不下,二维线段树时间吃不消,于是可以考虑树状数组套线段树。(动态开点,空间
Code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+110;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
int n,k,ans[N];
struct node{
int x,y,z;
bool operator<(const node &A)const{
if(x==A.x){
if(y==A.y)return z<A.z;
return y<A.y;
}
return x<A.x;
}
bool operator==(const node &A)const{return x==A.x && y==A.y && z==A.z;}
}a[N];
int cnt,val[N<<6],ls[N<<6],rs[N<<6];
struct SegmentTree{
int rt;
void pushup(int u){
val[u]=val[ls[u]]+val[rs[u]];return;//已知cnt[0]=0;
}
void update(int &u,int l,int r,int x,int t){
if(!u)u=++cnt;
if(l==r){val[u]+=t;return;}
int mid=(l+r)>>1;
if(x<=mid)update(ls[u],l,mid,x,t);
if(x>mid)update(rs[u],mid+1,r,x,t);
pushup(u);return;
}
int query(int u,int l,int r,int L,int R){
if(!u)return 0;
if(L<=l && r<=R)return val[u];
int mid=(l+r)>>1,res=0;
if(L<=mid)res+=query(ls[u],l,mid,L,R);
if(R>mid)res+=query(rs[u],mid+1,r,L,R);
return res;
}
};
struct TreeArray{
SegmentTree E2[N];
void update(int x,int y,int t){
for(int i=x;i<=k;i+=i&(-i))
E2[i].update(E2[i].rt,1,k,y,t);
return;
}
int query(int x,int y){
int res=0;
for(int i=x;i>0;i-=i&(-i))
res+=E2[i].query(E2[i].rt,1,k,1,y);
return res;
}
}E1;
int main(){
n=read(),k=read();
for(int i=1;i<=n;i++)a[i].x=read(),a[i].y=read(),a[i].z=read();
sort(a+1,a+n+1);
int tmp=1;
for(int i=1;i<=n;i++){
if(a[i]==a[i+1] && i+1<=n){tmp++;continue;}
E1.update(a[i].y,a[i].z,tmp);
int id=E1.query(a[i].y,a[i].z)-1;
ans[id]+=tmp;tmp=1;
}
for(int i=0;i<n;i++)printf("%d\n",ans[i]);
return 0;
}
其实当你完成上面的学习后,线段树套平衡树也就不难理解的,树套树都这样,又有什么区别?
考虑将区间开成一棵线段树,线段树上每个节点开一棵平衡树。操作实现:
- 修改。直接把每个设计区间都删去原数,添加新数即可。
- 查询
排名。统计区间设计平衡树中小于 的数的数量。 - 查询排名为
的数。二分这个数,然后就是操作二。 - 查询前驱,所有前驱取最大值。
- 查询后驱,所有后驱取最小值。
ADD:这份代码的复杂度是
Code
#include<bits/stdc++.h>
using namespace std;
const int N=5e4+110,M=N*200;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
bool alarm1;
int n,m,a[M];
int cnt,rnd[M],ls[M],rs[M],val[M],sze[M];
struct FHQTree{
int rt,x,y,z;
void pushup(int u){sze[u]=sze[ls[u]]+sze[rs[u]]+1;return;}//sze[0]=0;
int newnode(int v){
rnd[++cnt]=rand();
val[cnt]=v;
sze[cnt]=1;
return cnt;
}
void split(int u,int v,int &A,int &B){
if(!u)A=B=0;
else{
if(val[u]<=v){A=u;split(rs[A],v,rs[A],B);}
else{B=u;split(ls[B],v,A,ls[B]);}
pushup(u);
}
return;
}
int merge(int A,int B){
if(!A || !B)return A+B;
if(rnd[A]<rnd[B]){rs[A]=merge(rs[A],B);pushup(A);return A;}
else{ls[B]=merge(A,ls[B]);pushup(B);return B;}
}
void insert(int v){
split(rt,v,x,y);
rt=merge(x,merge(newnode(v),y));
return;
}
void erase(int v){
split(rt,v,x,z);
split(x,v-1,x,y);//写成split(rt,v-1,x,y);调了我两天
y=merge(ls[y],rs[y]);
rt=merge(merge(x,y),z);
return;
}
int Smaller(int v){
split(rt,v-1,x,y);
int tmp=sze[x];
rt=merge(x,y);
return tmp;
}
int KthVal(int u,int k){
if(!u)return 0;
if(k<=sze[ls[u]])return KthVal(ls[u],k);
if(k<=sze[ls[u]]+1)return val[u];
return KthVal(rs[u],k-1-sze[ls[u]]);
}
int pre(int v){
split(rt,v-1,x,y);
int tmp;
if(sze[x]==0)tmp=-2147483647;
else tmp=KthVal(x,sze[x]);
rt=merge(x,y);return tmp;
}
int nxt(int v){
split(rt,v,x,y);
int tmp;
if(sze[y]==0)tmp=2147483647;
else tmp=KthVal(y,1);
rt=merge(x,y);return tmp;
}
// void Bark(int u){
// if(!u)return;
// cout<<val[u]<<endl;
// Bark(ls[u]);
// Bark(rs[u]);
// return;
// }
};
struct SegmentTree{
FHQTree E2[M];
void build(int u,int l,int r){
if(l==r){E2[u].insert(a[l]);return;}
int mid=(l+r)>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
for(int i=l;i<=r;i++)E2[u].insert(a[i]);
return;
}
void update(int u,int l,int r,int x,int k){//删除x位置上的数,添加y
if(l==r){
E2[u].erase(a[x]);E2[u].insert(k);
return;
}
int mid=(l+r)>>1;
if(x<=mid)update(u<<1,l,mid,x,k);
if(x>mid)update(u<<1|1,mid+1,r,x,k);
E2[u].erase(a[x]);E2[u].insert(k);
return;
}
int Smaller(int u,int l,int r,int L,int R,int v){
if(L<=l && r<=R)return E2[u].Smaller(v);
int mid=(l+r)>>1,res=0;
if(L<=mid)res+=Smaller(u<<1,l,mid,L,R,v);
if(R>mid)res+=Smaller(u<<1|1,mid+1,r,L,R,v);
return res;
}
int pre(int u,int l,int r,int L,int R,int v){
if(L<=l && r<=R)return E2[u].pre(v);
int mid=(l+r)>>1,res=-2147483647;
if(L<=mid)res=max(res,pre(u<<1,l,mid,L,R,v));
if(R>mid)res=max(res,pre(u<<1|1,mid+1,r,L,R,v));
return res;
}
int nxt(int u,int l,int r,int L,int R,int v){
if(L<=l && r<=R)return E2[u].nxt(v);
int mid=(l+r)>>1,res=2147483647;
if(L<=mid)res=min(res,nxt(u<<1,l,mid,L,R,v));
if(R>mid)res=min(res,nxt(u<<1|1,mid+1,r,L,R,v));
return res;
}
// void Bark(int u,int l,int r,int L,int R){
// if(L<=l && r<=R){
// cout<<"E1:"<<l<<" "<<r<<endl;
// E2[u].Bark(E2[u].rt);
// return;
// }
// int mid=(l+r)>>1;
// if(L<=mid)Bark(u<<1,l,mid,L,R);
// if(R>mid)Bark(u<<1|1,mid+1,r,L,R);
// return;
// }
}E1;
bool alarm2;
int main(){
srand('?');fprintf(stderr,"%.3lf",abs(&alarm1-&alarm2)/1024.0/1024.0);
n=read(),m=read();for(int i=1;i<=n;i++)a[i]=read();
E1.build(1,1,n);
for(int o=1;o<=m;o++){
int opt=read();
if(opt==1){
int l=read(),r=read(),k=read();
printf("%d\n",E1.Smaller(1,1,n,l,r,k)+1);
}
if(opt==2){
int l=read(),r=read(),k=read();
int ansl=0,ansr=1e8,ans;
while(ansl<=ansr){
int mid=(ansl+ansr)>>1;
if(E1.Smaller(1,1,n,l,r,mid)<=k-1)
ans=mid,ansl=mid+1;
else ansr=mid-1;
}
printf("%d\n",ans);
}
if(opt==3){
int x=read(),k=read();
E1.update(1,1,n,x,k);a[x]=k;
}
if(opt==4){
int l=read(),r=read(),k=read();
printf("%d\n",E1.pre(1,1,n,l,r,k));
}
if(opt==5){
int l=read(),r=read(),k=read();
printf("%d\n",E1.nxt(1,1,n,l,r,k));
}
}
return 0;
}
用树状数组套权值线段树维护一个二维前缀和,先求出初始逆序对数,然后每次删除时求出被删除的值的贡献(它后面比他小的数的数量
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10,M=N*128;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
int n,m,a[N],b[N],ans;
int cnt,ls[M],rs[M],val[M];
struct SegmentTree{
int rt;
void pushup(int u){val[u]=val[ls[u]]+val[rs[u]];return;}
void update(int &u,int l,int r,int x,int t){
if(!u)u=++cnt;
if(l==r){val[u]+=t;return;}
int mid=(l+r)>>1;
if(x<=mid)update(ls[u],l,mid,x,t);
if(x>mid)update(rs[u],mid+1,r,x,t);
pushup(u);return;
}
int query(int u,int l,int r,int L,int R){
if(!u)return 0;
if(L<=l && r<=R)return val[u];
int mid=(l+r)>>1,res=0;
if(L<=mid)res+=query(ls[u],l,mid,L,R);
if(R>mid)res+=query(rs[u],mid+1,r,L,R);
return res;
}
// void bark(int u,int l,int r){
// if(!u)return;
// if(l==r){
// cout<<l<<" "<<r<<" "<<val[u]<<endl;
// return;
// }
// int mid=(l+r)>>1;
// bark(ls[u],l,mid);
// bark(rs[u],mid+1,r);
// return;
// }
};
struct TreeArray{
SegmentTree E2[N];
void update(int x,int y,int t){
for(int i=x;i<=n;i+=i&(-i))
E2[i].update(E2[i].rt,1,n,y,t);
return;
}
int query(int x,int l,int r){
int res=0;
for(int i=x;i>0;i-=i&(-i))
res+=E2[i].query(E2[i].rt,1,n,l,r);
return res;
}
// void bark(){
// for(int i=1;i<=n;i++){
// cout<<"Now::"<<i<<endl;
// E2[i].bark(E2[i].rt,1,n);
// }
// return;
// }
}E1;
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++){
a[i]=read();b[a[i]]=i;
E1.update(i,a[i],1);
ans+=E1.query(i-1,a[i]+1,n);
}
for(int i=1;i<=m;i++){
printf("%lld\n",ans);
int x=read();
ans-=E1.query(b[x]-1,x+1,n);
ans-=E1.query(n,1,x-1)-E1.query(b[x],1,x-1);
E1.update(b[x],x,-1);
}
return 0;
}
我们发现,
但是出题人也发现自己出得太裸了,就想恶心一下我们,卡我们空间,我们可以在
Code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+110,M=N*148;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
bool alarm1;
int n,a[N],b[N],m;
int cyx[M],top;
int cnt,val[M],ls[M],rs[M];
struct SegmentTree{
int rt;
void pushup(int u){val[u]=val[ls[u]]+val[rs[u]];return;}
void update(int &u,int l,int r,int x,int t){
if(!u){
if(top)u=cyx[top--];
else u=++cnt;
}
if(l==r){val[u]+=t;return;}
int mid=(l+r)>>1;
if(x<=mid)update(ls[u],l,mid,x,t);
if(x>mid)update(rs[u],mid+1,r,x,t);
pushup(u);
if(!val[u])cyx[++top]=u,u=0;
return;
}
int query(int u,int l,int r,int L,int R){
if(!u)return 0;
if(L<=l && r<=R)return val[u];
int mid=(l+r)>>1,res=0;
if(L<=mid)res+=query(ls[u],l,mid,L,R);
if(R>mid)res+=query(rs[u],mid+1,r,L,R);
return res;
}
};
struct TreeArray{
SegmentTree E2[N];
void update(int x,int y,int t){
for(int i=x;i<=n;i+=i&(-i))
E2[i].update(E2[i].rt,1,n,y,t);
return;
}
int query(int x,int l,int r){
int res=0;
for(int i=x;i>0;i-=i&(-i))
res+=E2[i].query(E2[i].rt,1,n,l,r);
return res;
}
}E1;
bool alarm2;
int main(){
fprintf(stderr,"%.3lf",abs(&alarm1-&alarm2)/1024.0/1024.0);
n=read(),m=read();
for(int i=1;i<=n;i++)a[read()]=i;
for(int i=1;i<=n;i++)b[i]=a[read()];
for(int i=1;i<=n;i++)E1.update(i,b[i],1);
for(int i=1;i<=m;i++){
int opt=read();
if(opt==1){
int l1=read(),r1=read(),l2=read(),r2=read();
printf("%d\n",E1.query(r2,l1,r1)-E1.query(l2-1,l1,r1));
}
if(opt==2){
int x=read(),y=read();
E1.update(x,b[x],-1);
E1.update(y,b[y],-1);
swap(b[x],b[y]);
E1.update(x,b[x],1);
E1.update(y,b[y],1);
}
}
return 0;
}
我们观察到,如果这题是个单点修改,就是个裸的的带修线段树,但是它是区间修改,我们怎么办呢?考虑到外层是个树状数组,树状数组其实也是支持区间修改的,于是我便将树状数组的区间修改区间查询操作套到了树套树上(其实这题直接线段树套值域线段树也能过),结果树状数组写错调了半天。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e4+110,M=N*128;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
int n,m;
int opt[N],ql[N],qr[N],cc[N],lsh[N],LshCnt,Lshed;
int ls[M],rs[M],cnt,val[M];
struct SegmentTree{
int rt=0;
void pushup(int u){val[u]=val[ls[u]]+val[rs[u]];return;}
void update(int &u,int l,int r,int x,int t){
if(!u)u=++cnt;
if(l==r){val[u]+=t;return;}
int mid=(l+r)>>1;
if(x<=mid)update(ls[u],l,mid,x,t);
if(x>mid)update(rs[u],mid+1,r,x,t);
pushup(u);return;
}
};
struct TreeArray{
SegmentTree SonTree[N];
void update(int x,int y,int t){
for(int i=x;i<=n;i+=i&(-i))
SonTree[i].update(SonTree[i].rt,1,Lshed,y,t);
return;
}
}E,Ei;
vector<int>rcdu,rcdv,iu,iv;
int query(int l,int r,int k,int cs1,int cs2){
if(l==r)return l;
int SRson=0,mid=(l+r)>>1;
for(int i=0;i<(int)rcdu.size();i++)SRson+=val[rs[rcdu[i]]]*(cs1+1);
for(int i=0;i<(int)iu.size();i++)SRson-=val[rs[iu[i]]];
for(int i=0;i<(int)rcdv.size();i++)SRson-=val[rs[rcdv[i]]]*(cs2+1);
for(int i=0;i<(int)iv.size();i++)SRson+=val[rs[iv[i]]];
if(SRson<k){
for(int i=0;i<(int)rcdu.size();i++)rcdu[i]=ls[rcdu[i]];
for(int i=0;i<(int)iu.size();i++)iu[i]=ls[iu[i]];
for(int i=0;i<(int)rcdv.size();i++)rcdv[i]=ls[rcdv[i]];
for(int i=0;i<(int)iv.size();i++)iv[i]=ls[iv[i]];
return query(l,mid,k-SRson,cs1,cs2);
}
else{
for(int i=0;i<(int)rcdu.size();i++)rcdu[i]=rs[rcdu[i]];
for(int i=0;i<(int)iu.size();i++)iu[i]=rs[iu[i]];
for(int i=0;i<(int)rcdv.size();i++)rcdv[i]=rs[rcdv[i]];
for(int i=0;i<(int)iv.size();i++)iv[i]=rs[iv[i]];
return query(mid+1,r,k,cs1,cs2);
}
}
int PreQuery(int l,int r,int k){
rcdu.clear();iu.clear();
rcdv.clear();iv.clear();
for(int i=r;i>0;i-=i&(-i))rcdu.push_back(E.SonTree[i].rt);
for(int i=l-1;i>0;i-=i&(-i))rcdv.push_back(E.SonTree[i].rt);
for(int i=r;i>0;i-=i&(-i))iu.push_back(Ei.SonTree[i].rt);
for(int i=l-1;i>0;i-=i&(-i))iv.push_back(Ei.SonTree[i].rt);
return query(1,Lshed,k,r,l-1);
}
signed main(){
n=read(),m=read();
for(int i=1;i<=m;i++){
opt[i]=read(),ql[i]=read(),qr[i]=read(),cc[i]=read();
if(opt[i]==1)lsh[++LshCnt]=cc[i];
}
sort(lsh+1,lsh+LshCnt+1);
Lshed=unique(lsh+1,lsh+LshCnt+1)-lsh-1;
for(int i=1;i<=m;i++)if(opt[i]==1)cc[i]=lower_bound(lsh+1,lsh+Lshed+1,cc[i])-lsh;
for(int i=1;i<=m;i++){
if(opt[i]==1){
E.update(ql[i],cc[i],1);
E.update(qr[i]+1,cc[i],-1);
Ei.update(ql[i],cc[i],ql[i]);
Ei.update(qr[i]+1,cc[i],-qr[i]-1);
}
if(opt[i]==2)
printf("%lld\n",lsh[PreQuery(ql[i],qr[i],cc[i])]);
}
return 0;
}
我用的树状数组套值域线段树。结果没想到离散化竟然会有笨蛋写错还调了那么久。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10,M=N*200;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
int n,m,a[N],ans;
int lsh[N],LshCnt;
int cnt,ls[M],rs[M],val[M];
struct SegmentTree{
int rt;
void pushup(int u){val[u]=val[ls[u]]+val[rs[u]];return;}
void update(int &u,int l,int r,int x,int t){
if(!u)u=++cnt;
if(l==r){val[u]+=t;return;}
int mid=(l+r)>>1;
if(x<=mid)update(ls[u],l,mid,x,t);
if(x>mid)update(rs[u],mid+1,r,x,t);
pushup(u);return;
}
int query(int u,int l,int r,int L,int R){
if(!u)return 0;
if(L<=l && r<=R)return val[u];
int mid=(l+r)>>1,res=0;
if(L<=mid)res+=query(ls[u],l,mid,L,R);
if(R>mid)res+=query(rs[u],mid+1,r,L,R);
return res;
}
// void bark(int u,int l,int r){
// if(!u)return;
// cout<<l<<" "<<r<<" "<<val[u]<<endl;
// if(l==r){
// return;
// }
// int mid=(l+r)>>1;
// bark(ls[u],l,mid);
// bark(rs[u],mid+1,r);
// return;
// }
};
struct TreeArray{
SegmentTree E2[N];
void update(int x,int y,int t){
for(int i=x;i<=n;i+=i&(-i))
E2[i].update(E2[i].rt,1,LshCnt,y,t);
return;
}
int query(int x,int l,int r){
int res=0;
for(int i=x;i>0;i-=i&(-i))
res+=E2[i].query(E2[i].rt,1,LshCnt,l,r);
return res;
}
// void bark(){
// for(int i=1;i<=n;i++){
// cout<<"Now::"<<i<<endl;
// E2[i].bark(E2[i].rt,1,LshCnt);
// }
// return;
// }
}E1;
signed main(){
n=read();
for(int i=1;i<=n;i++)lsh[i]=a[i]=read();
sort(lsh+1,lsh+n+1);
LshCnt=unique(lsh+1,lsh+n+1)-lsh-1;
for(int i=1;i<=n;i++)a[i]=lower_bound(lsh+1,lsh+LshCnt+1,a[i])-lsh;
for(int i=1;i<=n;i++){
E1.update(i,a[i],1);
ans+=E1.query(i-1,a[i]+1,LshCnt);
}
m=read();
printf("%lld\n",ans);
for(int i=1;i<=m;i++){
int x=read(),y=read();
ans-=E1.query(x-1,a[x]+1,LshCnt);
ans-=E1.query(n,1,a[x]-1)-E1.query(x,1,a[x]-1);
E1.update(x,a[x],-1);
ans-=E1.query(y-1,a[y]+1,LshCnt);
ans-=E1.query(n,1,a[y]-1)-E1.query(y,1,a[y]-1);
E1.update(y,a[y],-1);
swap(a[x],a[y]);
ans+=E1.query(x-1,a[x]+1,LshCnt);
ans+=E1.query(n,1,a[x]-1)-E1.query(x,1,a[x]-1);
E1.update(x,a[x],1);
ans+=E1.query(y-1,a[y]+1,LshCnt);
ans+=E1.query(n,1,a[y]-1)-E1.query(y,1,a[y]-1);
E1.update(y,a[y],1);
printf("%lld\n",ans);
}
return 0;
}
畸形树套树,其实是运用了树套树的思想。确切来说是图套树。考虑编号和权值有两个维度,二维偏序求
Code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+110,M=N*128;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
int val[M],ls[M],rs[M],cnt;
struct Segment{
int rt;
void pushup(int u){val[u]=max(val[ls[u]],val[rs[u]]);return;}
void update(int &u,int l,int r,int x,int t){
if(!u)u=++cnt;
if(l==r){val[u]=max(val[u],t);return;}
int mid=(l+r)>>1;
if(x<=mid)update(ls[u],l,mid,x,t);
if(x>mid)update(rs[u],mid+1,r,x,t);
pushup(u);
return;
}
int query(int u,int l,int r,int L,int R){
if(!u)return 0;
if(L<=l && r<=R)return val[u];
int mid=(l+r)>>1,res=0;
if(L<=mid)res=max(res,query(ls[u],l,mid,L,R));
if(R>mid)res=max(res,query(rs[u],mid+1,r,L,R));
return res;
}
}tree[N];
int n,m,Led,ans;
int U[N],V[N],W[N];
int main(){
n=read(),m=read();
for(int i=1;i<=m;i++){
U[i]=read(),V[i]=read(),W[i]=read()+1;
Led=max(Led,W[i]);
}
for(int i=1;i<=m;i++){
int u=U[i],v=V[i];
int tmp=tree[u].query(tree[u].rt,1,Led,1,W[i]-1);
tree[v].update(tree[v].rt,1,Led,W[i],tmp+1);
}
for(int u=1;u<=n;u++)
ans=max(ans,tree[u].query(tree[u].rt,1,Led,1,Led));
printf("%d\n",ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探