CDQ分治+扫描线——(COGS577蝗灾,矩形面积的并)
首先 CDQ分治的重点是分治,实质是通过分治将多维偏序问题降维,直观体现比如,有修改和查询的操作,每次操作都保证前一半只取修改,后一半只取查询,然后进行计算,这样总能保证修改在查询前面,随着分治不断地进行,最终所有在当前查询点前的所有修改都会对其产生贡献,
就CDQ蝗虫的例题来讲,CDQ解决的是时间问题,就是上边的例子,它通过某种广义上的排序,使得我们不再需要按照时间顺序依次修改查询,而是通过分治实现批量修改,批量查询。
单独说CDQ是非常难以理解的,博主也是看了好多CDQ博客,但是发现写的都很少,也没看懂。
原因就在于我感受不到CDQ的降维操作。
就蝗虫例题来讲:通过CDQ分治,我们将问题分解成几个子问题,并且是比较简单的问题:假如所有的修改都在查询前面,问如何做 n≤106n\le 10^6n≤106
单点修改,二维区间查询面积和,正常会想到的是二维前缀和,因为我们面对二维平面的求和问题,只会前缀和,所以我们想不到合理的解决方式也是正常的。
这时候,扫描线就出来了,我们再来说说扫描线,我同样也是看了很久,一直没有看懂,原因在于我只一直找扫描线的板子, 而实际上,扫描线就是本意:一条竖直的线横着扫过去。而通过这种方式,恰恰可以实现二维平面内的区间操作,最直观的就是面积操作。
如何实现呢,其实就是用一个一维的数据结构比如:树状数组,线段树等等,当成 “扫描线”,然后扫过去,用数据结构不断维护每次移动的状态,因此来解决相应的问题。
有人就会疑惑:横着咋扫过去,就直接枚举吗?还有就是数据结构维护状态,解决相应问题,这个好抽象。
确实难以理解,因为没有具体例子,这里给出两个例子:
- 扫描线模板——求面积并,其中 “扫” 是直接存储了各个矩形面积的 x 位置,从而构成了一个个小区间,从过枚举这些x,对于面积贡献问题,可以提供 ”高线“,即两个数之差,而不是盲目的每个x的位置都枚举。
- 但是在CDQ蝗灾里面,x只起到了排序的作用
第二个问题:维护相应的问题:
- 求面积并使用线段树当竖线,并且维护的元素是当前区间最小值,和最小值的数量
- 而CDQ蝗灾中需要维护的则是支持单点修改,区间查询的数据结构,直观的来看,面对二维前缀和问题,我们可以把他当成是n个前缀和,然后去做,这个时候实际上就是竖着的一个数据机构在横着走,不断维护每一行的前缀和,所以在求解的时候,我们只需要标记区间左右端点,在该点位置进行操作就可以得出前缀和
通过这两道题,我也很很难说出CDQ和扫描线的具体模板,代码上直观的感受是线段树和树状数组维护,思想上直观的看就是偏序问题降维,说人话就是多元问题,但从时间方面看是无序问题,通过这些方法实现降维问题,CDQ的时间降维,扫描线的x降维
总结,CDQ分治实现的是三维偏序问题降维二维偏序问题,扫描线解决的是二维偏序问题降维到一维偏序问题,然后再用一维数据结构进行维护解决。
扫描线代码
/*
线段树维护最小值并且维护数量,也就是维护0的数量
离散化X轴来建树
排序y轴,开始扫描
我现在明白为甚一开始我是将 y 作为一维数据结构维护处,因为都是排序 x ,这样更直观
*/
#include<bits/stdc++.h>
#define int long long
#define root 1,len-1,1
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
using namespace std;
int read(){int x;scanf("%lld",&x);return x;}
const int B=8e5+10;
const int inf=0x3f3f3f3f;
int T;
int b[B];
int len;
namespace Seg
{
struct node
{
int l,r,col,minxx,tot;
node(){l=r=col=minxx=tot=0;minxx=0x3f3f3f3f;}
void init(int l_,int r_){l=l_,r=r_,col=0,minxx=0,tot=b[r+1]-b[l];}
}z[B<<2];
node operator +(const node &l,const node &r)
{
node p;
p.l=l.l,p.r=r.r;
p.minxx=min(l.minxx,r.minxx);
if (p.minxx==l.minxx) p.tot+=l.tot;
if (p.minxx==r.minxx) p.tot+=r.tot;
p.col=0;
return p;
}
void color(int rt,int v){z[rt].col+=v;z[rt].minxx+=v;}
void updata(int rt) {z[rt]=z[rt<<1]+z[rt<<1|1];}
void push(int rt)
{
if (z[rt].col)
{
color(rt<<1,z[rt].col);
color(rt<<1|1,z[rt].col);
z[rt].col=0;
}
}
void build(int l,int r,int rt)
{
if (l==r) {z[rt].init(l,r);return;}
int m=l+r>>1;
build(lson);
build(rson);
updata(rt);
}
void modify(int l,int r,int rt,int nowl,int nowr,int v)
{
if (nowl<=l && r<=nowr) {color(rt,v);return;}
int m=l+r>>1;
push(rt);
if (nowl<=m) modify(lson,nowl,nowr,v);
if (m<nowr) modify(rson,nowl,nowr,v);
updata(rt);
}
node query(int l,int r,int rt,int nowl,int nowr)
{
if (nowl<=l && r<=nowr) return z[rt];
push(rt);
int m=l+r>>1;
if (nowl<=m)
{
if (m<nowr) return query(lson,nowl,nowr)+query(rson,nowl,nowr);
return query(lson,nowl,nowr);
}
else return query(rson,nowl,nowr);
}
}
int n,m;
struct node
{
int y;
int xl,xr;
int val;
}a[B<<2];
int cmp(node a,node b)
{
return a.y<b.y;
}
int tota;
int totb;
void work()
{
cin>>n;
for (int i=1;i<=n;i++)
{
int xl=read(),yl=read(),xr=read(),yr=read();
b[++totb]=xl;
b[++totb]=xr;
a[++tota]={yl,xl,xr,1};
a[++tota]={yr,xl,xr,-1};
}
sort(b+1,b+1+totb);
len=unique(b+1,b+1+totb)-(b+1);
Seg::build(root);
for (int i=1;i<=tota;i++)
{
a[i].xl=lower_bound(b+1,b+1+len,a[i].xl)-b;
a[i].xr=lower_bound(b+1,b+1+len,a[i].xr)-b;
}
sort(a+1,a+1+tota,cmp);
int ans=0;
for (int i=1;i<=tota;i++)
{
if (a[i].xl!=a[i].xr) Seg::modify(root,a[i].xl,a[i].xr-1,a[i].val);
Seg::node Ans=Seg::query(root,1,len-1);
ans+=((b[len]-b[1])-(Ans.minxx==0)*Ans.tot)*(a[i+1].y-a[i].y);
}
cout<<ans;
}
signed main()
{
T=1;
while (T--) work();
return 0;
}
CDQ蝗灾代码
int T;
int n;
int m;
struct node
{
int opt,xl,xr,yl,yr,k;
}a[B];
struct node2
{
int opt,x,yl,yr,val,id;
}p[B];
int cmp(node2 a,node2 b)
{
if(a.x==b.x) return a.opt<b.opt;
return a.x<b.x;
}
int tot;
int t[B];
int ans[B];
int lowbit(int x){return x&(-x);}
void modify(int x,int v) {for (int i=x;i<=n;i+=lowbit(i)) t[i]+=v;}
int query(int x){int res=0;for (int i=x;i;i-=lowbit(i)) res+=t[i];return res;}
int ask(int l,int r){return query(r)-query(l-1);}
void solve(int l,int r)
{
if (l==r) return;
tot=0;
int mid=l+r>>1;
for (int i=l;i<=mid;i++) if (a[i].opt==1) p[++tot]=(node2){a[i].opt,a[i].xl,a[i].yl,0,a[i].k,0};
for (int i=mid+1;i<=r;i++)
{
if (a[i].opt==2)
{
p[++tot]=(node2){a[i].opt,a[i].xl,a[i].yl,a[i].yr,0,i};
p[++tot]=(node2){a[i].opt+1,a[i].xr,a[i].yl,a[i].yr,0,i};
}
}
sort(p+1,p+1+tot,cmp);
for (int i=1;i<=tot;i++)
{
if (p[i].opt==1) modify(p[i].yl,p[i].val);
if (p[i].opt==2) ans[p[i].id]-=ask(p[i].yl,p[i].yr);
if (p[i].opt==3) ans[p[i].id]+=ask(p[i].yl,p[i].yr);
}
for (int i=1;i<=tot;i++) if (p[i].opt==1) modify(p[i].yl,-p[i].val);
solve(l,mid);
solve(mid+1,r);
}
void work()
{
freopen("locust.in","r",stdin);
freopen("locust.out","w",stdout);
cin>>n;
cin>>m;
for (int i=1;i<=m;i++)
{
a[i].opt=read();
if (a[i].opt==1)
{
a[i].xl=read();
a[i].yl=read();
a[i].k=read();
}
else
{
int xl=read(),yl=read(),xr=read(),yr=read();
a[i]={2,min(xl,xr),max(xl,xr),min(yl,yr),max(yl,yr),0};
}
}
solve(1,m);
for (int i=1;i<=m;i++)
{
if (a[i].opt==2) cout<<ans[i]<<"\n";
}
}
signed main()
{
T=1;
while (T--) work();
return 0;
}
之所以复习这些算法,起初是因为一道题目的题解说通过…转化,这不就是CDQ板子提了吗,然后我就忘记了CDQ解决什么问题了,所以就来看以前CDQ的笔记,解决发现,我题解里说了一句,这个问题的弱化版可以直接用扫描线去做,然后我又去学习扫描线,写代码的过程中顺带复习了线段树和树状数组,好一个顺藤摸瓜。