[做题笔记] lxl 的数据结构选讲(下)
rrusq
题目描述
解法
考虑扫描矩形的右端点,维护所有左端点的答案。
考虑加入一个矩形的影响,可以用染色模型来理解,也就是把在这个矩形内的点染成这个矩形编号的颜色,那么查询只需要找颜色 \(\geq l\) 的点权值和即可。
考虑如何快速染色并且维护点权,一个显然的想法是颜色段均摊,因为本题是二维意义下的,所以我们可以在 \(\tt kdt\) 上颜色段均摊。具体来说就是在给一个点打标记时,直接暴力清空在子树中的标记,颜色标记需要在递归前下传。
打标记时可以拿一个全局的数据结构来支持修改和点权查询,我们可以使用 \(O(1)-O(\sqrt n)\) 的分块技术。
注意本题操作的特殊性,\(\tt kdt\) 要类似线段树来建立(非叶节点代表区间,不代表实际的点);复杂度基于,对于每个矩形打标记的时间和删除标记的时间是一样的,所以总时间复杂度 \(O(m\sqrt n+q\sqrt m)\)
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int M = 100005;
const int Z = M<<2;
const int N = 1000005;
const int inf = 0x3f3f3f3f;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
void write(int x)
{
if(x>=10) write(x/10);
putchar(x%10+'0');
}
int n,m,q,A[Z],B[Z],C[Z],D[Z];
int xl,xr,yl,yr,s[Z],col[Z],vis[Z];
struct node{int x,y,v;}a[M];vector<int> v[M];
struct hhz{int a,b,c,d;}t[M];int ans[N],L[N];
struct zxy
{
int n,a[M],b[M],fl[M],L[M],R[M];
void init()
{
int m=sqrt(n);
for(int i=1;i<=n;i++)
{
b[i]=(i-1)/m+1;
if(!L[b[i]]) L[b[i]]=i;
R[b[i]]=i;
}
}
void add(int x,int y) {a[x]+=y;fl[b[x]]+=y;}
int ask(int x)
{
int r=0;
for(int i=b[x]+1;i<=b[n];i++) r+=fl[i];
for(int i=x;i<=R[b[x]];i++) r+=a[i];
return r;
}
}H;
void build(int x,int l,int r,int w)
{
if(l==r)
{
s[x]=a[l].v;
A[x]=B[x]=a[l].x;
C[x]=D[x]=a[l].y;
return ;
}
int mid=(l+r)>>1;
nth_element(a+l,a+mid,a+1+r,[&](node u,node v)
{return w==0?(u.x<v.x):(u.y<v.y);});
build(x<<1,l,mid,w^1);
build(x<<1|1,mid+1,r,w^1);
A[x]=min(A[x<<1],A[x<<1|1]);
B[x]=max(B[x<<1],B[x<<1|1]);
C[x]=min(C[x<<1],C[x<<1|1]);
D[x]=max(D[x<<1],D[x<<1|1]);
s[x]=s[x<<1]+s[x<<1|1];
}
int out(int x)
{
return A[x]>xr || B[x]<xl || C[x]>yr || D[x]<yl;
}
int in(int x)
{
return xl<=A[x] && B[x]<=xr && yl<=C[x] && D[x]<=yr;
}
void down(int x)
{
if(!col[x]) return ;
col[x<<1]=col[x<<1|1]=col[x];
vis[x<<1]=vis[x<<1|1]=1;col[x]=0;
}
void clr(int x)
{
if(!vis[x]) return ;vis[x]=0;
if(col[x])
{
H.add(col[x],-s[x]);
col[x]=0;return ;
}
clr(x<<1);clr(x<<1|1);
}
void dfs(int x,int y)
{
if(out(x)) return ;
if(in(x))
{
clr(x);col[x]=y;vis[x]=1;
H.add(y,s[x]);return ;
}
down(x);dfs(x<<1,y);dfs(x<<1|1,y);
vis[x]=1;
}
signed main()
{
n=read();A[0]=C[0]=inf;
for(int i=1;i<=n;i++)
a[i].x=i,a[i].y=read(),a[i].v=read();
build(1,1,n,0);
m=read();H.n=m;H.init();
for(int i=1;i<=m;i++)
t[i].a=read(),t[i].b=read(),
t[i].c=read(),t[i].d=read();
q=read();
for(int i=1;i<=q;i++)
L[i]=read(),v[read()].push_back(i);
for(int i=1;i<=m;i++)
{
xl=t[i].a;xr=t[i].b;
yl=t[i].c;yr=t[i].d;
dfs(1,i);
for(int x:v[i]) ans[x]=H.ask(L[x]);
}
for(int i=1;i<=q;i++)
write(ans[i]),puts("");
}
rprmq1
题目描述
解法
二维平面可以视为有 \(n\) 个版本的,长度为 \(n\) 的序列。设修改为五元组 \((l,r,x,y,c)\),可以把它拆分成两部分,把 \((x,y,c)\) 放在 \(l\) 处,把 \((x,y,-c)\) 放在 \(r+1\) 处。对于一个版本,我们只需要加入放在它前面的所有修改即可。
那么对于一个询问 \((l,r,x,y)\),我们需要在 \([l,r]\) 这些版本中询问区间 \([x,y]\) 的最大值。
考虑猫树分治,对于过终点的询问,可以把它拆成两部分,然后维护历史最大值即可。猫树分治的作用在于:提供了一个版本的起点,把若干个版本分别求最大值的问题转化成了历史最大值问题。
先拿从中点向右扫来举例(中点向左扫类似),设现在访问的到的版本是 \(x\),我们需要记录 \((mid,x]\) 这些版本的历史最大值。可以在开始扫的时候把历史最大值清空(线段树上维护清空标记来实现),扫描的时候线段树维护历史最大值即可。
由于需要提前加入位于 \([1,l)\) 的这些修改,所以需要维护一个全局的指针,时间复杂度 \(O(n\log^2 n+q\log n)\)
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 500005;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
void write(int x)
{
if(x>=10) write(x/10);
putchar(x%10+'0');
}
int n,m,q,ans[M];
struct zxy{int id,l,r,x,y;}a[M];vector<zxy> b[M];
struct node{int l,r,c;};vector<node> v[M];
//
int fl[M],hf[M],mx[M],hm[M],cov[M];
void add(int i,int v1,int v2)
{
hm[i]=max(hm[i],v2+mx[i]);mx[i]+=v1;
hf[i]=max(hf[i],v2+fl[i]);fl[i]+=v1;
}
void reset(int i)
{
add(i<<1,fl[i],hf[i]);
add(i<<1|1,fl[i],hf[i]);
fl[i]=hf[i]=0;hm[i]=mx[i];cov[i]=1;
}
void down(int i)
{
if(cov[i]) reset(i<<1),reset(i<<1|1),cov[i]=0;
add(i<<1,fl[i],hf[i]);
add(i<<1|1,fl[i],hf[i]);
fl[i]=hf[i]=0;
}
void up(int i)
{
mx[i]=max(mx[i<<1],mx[i<<1|1]);
hm[i]=max(hm[i<<1],hm[i<<1|1]);
}
void Add(int i,int l,int r,int L,int R,int c)
{
if(L>r || l>R) return ;
if(L<=l && r<=R) {add(i,c,c);return ;}
int mid=(l+r)>>1;down(i);
Add(i<<1,l,mid,L,R,c);
Add(i<<1|1,mid+1,r,L,R,c);
up(i);
}
int ask(int i,int l,int r,int L,int R)
{
if(L>r || l>R) return 0;
if(L<=l && r<=R) return hm[i];
int mid=(l+r)>>1;down(i);
return max(ask(i<<1,l,mid,L,R),
ask(i<<1|1,mid+1,r,L,R));
}
//
void ins(int i,int l,int r,zxy p)
{
int mid=(l+r)>>1;
if(p.l<=mid+1 && mid<=p.r)
{b[i].push_back(p);return ;}
if(p.r<=mid) ins(i<<1,l,mid,p);
else ins(i<<1|1,mid+1,r,p);
}
int cmp(node a,node b) {return a.c<b.c;}
int cmp1(zxy a,zxy b) {return a.r<b.r;}
int cmp2(zxy a,zxy b) {return a.l>b.l;}
void get(int i,int op)
{
if(op==1) for(auto x:v[i])
Add(1,1,n,x.l,x.r,x.c);
else if(v[i].size())
for(int j=v[i].size()-1;j>=0;j--)
Add(1,1,n,v[i][j].l,v[i][j].r,-v[i][j].c);
}
void dfs(int i,int l,int r)
{
int mid=(l+r)>>1,k=0,j=1;
for(int i=l;i<=mid;i++) get(i,1);
for(auto x:b[i]) a[++k]=x;
sort(a+1,a+1+k,cmp1);
while(j<=k && a[j].r==mid) j++;
for(int i=mid+1;i<=r;i++)
{
get(i,1);
if(i==mid+1) reset(1);
while(j<=k && a[j].r==i)
{
ans[a[j].id]=max(ans[a[j].id],
ask(1,1,n,a[j].x,a[j].y));j++;
}
}
for(int i=r;i>=mid+1;i--) get(i,-1);
if(l!=r) dfs(i<<1|1,mid+1,r);
//
k=0;j=1;
for(auto x:b[i]) a[++k]=x;
sort(a+1,a+1+k,cmp2);
while(j<=k && a[j].l==mid+1) j++;
for(int i=mid;i>=l;i--)
{
if(i==mid) reset(1);
while(j<=k && a[j].l==i)
{
ans[a[j].id]=max(ans[a[j].id],
ask(1,1,n,a[j].x,a[j].y));j++;
}
get(i,-1);
}
if(l!=r) dfs(i<<1,l,mid);
}
signed main()
{
n=read();m=read();q=read();
for(int i=1;i<=m;i++)
{
int l=read(),x=read(),r=read(),y=read(),c=read();
v[l].push_back(node{x,y,c});
v[r+1].push_back(node{x,y,-c});
}
for(int i=1;i<=n;i++)
sort(v[i].begin(),v[i].end(),cmp);
for(int i=1;i<=q;i++)
{
int l=read(),x=read(),r=read(),y=read();
ins(1,1,n,zxy{i,l,r,x,y});
}
dfs(1,1,n);
for(int i=1;i<=q;i++) write(ans[i]),puts("");
}