CDQ分治部分

CDQ分治是用分治的方法解决一系列类似偏序问题的分治方法,一般可以用KD-tree、树套树或权值线段树代替。

三维偏序,是一种类似LIS的东西,但是LIS的关键字只有两个,数组下标和权值,三维偏序问题的权值有两个,且必须A[I]<A[J]且B[I]<B[j]。

把这个问题放到平面上,就是一个点在另一个点的左下方。

那么如何求?

CDQ分治的主要过程是二分整个区间,把左区间看成产生贡献的区间,于是我们在左区间进行操作,在右区间统计答案,用归并排序的方法求解。

对于这道题,我们二分整个区间,对A做归并排序,归并时对B维护一个树状数组,在左区间更改,右区间查询就可以了。

例题1

陌上花开(bzoj3262)裸的三维偏序

我们按照a为第一关键字,b为第二关键字,c为第三关键字排序,其实相当于把a去掉了,换成了数组下标,因为本质相同的元素答案是相同的,所以手动去重后直接CDQ分治求解。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#define low (x&(-x))
#define N 200009
#define M 100009
using namespace std;
typedef long long ll;
int k,n,ans2[M],be[M];
ll f[M],tr[N],ans[M];
struct zzh
{
int a,b,c,id,size;
bool operator < (const zzh &u)const
{
if(b!=u.b)return b<u.b;
if(c!=u.c)return c<u.c;
return id<u.id;
}
}ji[M],a[M],t[M];
bool cmp(zzh a,zzh b)
{
if(a.a!=b.a)return a.a<b.a;
if(a.b!=b.b)return a.b<b.b;
if(a.c!=b.c)return a.c<b.c;
return a.id<b.id;
}
void mem(int x){while(x<=k)tr[x]=,x+=low;}
void gai(int x,int y){while(x<=k)tr[x]+=y,x+=low;}
int query(int x)
{
int ans=;
while(x){ans+=tr[x];x-=low;}
return ans;
}
void cdq(int l,int r)
{
if(l==r)return;
int mid=(l+r)>>;
cdq(l,mid);cdq(mid+,r);
int p=l,q=mid+,o=l-;
while(p<=mid&&q<=r)
{
if(t[p]<t[q])
gai(t[p].c,t[p].size),ji[++o]=t[p++];
else ans[t[q].id]+=query(t[q].c),ji[++o]=t[q++];
}
while(p<=mid)ji[++o]=t[p++];
while(q<=r)ans[t[q].id]+=query(t[q].c),ji[++o]=t[q++];
for(int i=l;i<=r;++i)
mem(ji[i].c),t[i]=ji[i];
}
bool pd(int x,int now){
if(!now)return ;
return ((a[x].a==a[now].a)&&(a[x].b==a[now].b)&&(a[x].c==a[now].c));
}
int main()
{
// freopen("233.out","w",stdout);
scanf("%d%d",&n,&k);
for(int i=;i<=n;++i)
scanf("%d%d%d",&a[i].a,&a[i].b,&a[i].c),a[i].id=i,a[i].size=;
sort(a+,a+n+,cmp);int now=;
for(int i=;i<=n;++i)
{
if(!pd(i,i-))
t[++now]=a[i],t[now].id=now;
else t[now].size++;
be[i]=now;
}
cdq(,now);
for(int i=;i<=now;++i)ans[t[i].id]+=t[i].size-;
for(int i=;i<=n;++i)f[i]=ans[be[i]];
for(int i=;i<=n;++i)ans2[f[i]]++;
for(int i=;i<n;++i)printf("%d\n",ans2[i]);
return ;
}

bzoj4553 [Tjoi2016&Heoi2016]序列

也是一个类LIS问题,只不过条件有些特殊。

观察题目可知,i和j都在LIS中需满足a[i]<=a[j]&&a[i]<=l[j]&&r[i]<=a[j],其中l[i]为a[i]的最小值,r[i]为其最大值,第一个条件可省略,再加上下标,正好是三个。

我们先CDQ左区间,再把左区间按照a排序,右区间按照l排序,归并时将左区间的r加入树状数组,右区间用a查询,做完后在CDQ右区间就行了。

f数组记得取max。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100009
#define shi 100000
#define lowbit(x) (x&(-x))
using namespace std;
int tr[N],n,m,f[N];
inline int rd()
{
int x=;
char c=getchar();
while(!isdigit(c))c=getchar();
while(isdigit(c))
{
x=(x<<)+(x<<)+(c^);
c=getchar();
}
return x;
}
struct zzh
{
int l,r,a,id;
}ji[N];
void add(int x,int y){while(x<=shi){tr[x]=max(tr[x],y);x+=lowbit(x);}}
void clear(int x){while(x<=shi){tr[x]=;x+=lowbit(x);}}
int query(int x){
int ans=;
while(x){
ans=max(tr[x],ans);
x-=lowbit(x);
}
return ans;
}
bool cmp1(zzh a,zzh b){return a.a<b.a;}
bool cmp2(zzh a,zzh b){return a.l<b.l;}
bool cmp3(zzh a,zzh b){return a.id<b.id;}
void cdq(int l,int r)
{
if(l==r)return;
int mid=(l+r)>>;
cdq(l,mid);
sort(ji+l,ji+mid+,cmp1);sort(ji+mid+,ji+r+,cmp2);
int p=l,q=mid+,o=l-;
while(p<=mid&&q<=r)
{
if(ji[p].a<=ji[q].l)add(ji[p].r,f[ji[p].id]),p++;
else f[ji[q].id]=max(query(ji[q].a)+,f[ji[q].id]),q++;
}
while(q<=r)f[ji[q].id]=max(query(ji[q].a)+,f[ji[q].id]),q++;
for(int i=l;i<=mid;++i)clear(ji[i].r);
sort(ji+l,ji+r+,cmp3);
cdq(mid+,r);
}
int main()
{
n=rd();m=rd();
for(int i=;i<=n;++i)
ji[i].l=ji[i].r=ji[i].a=rd(),ji[i].id=i;
int x,y;
for(int j=;j<=m;++j)
{
x=rd();y=rd();
ji[x].l=min(ji[x].l,y);
ji[x].r=max(ji[x].r,y);
}
for(int i=;i<=n;++i)f[i]=;
cdq(,n);int ans=;
for(int i=;i<=n;++i)ans=max(ans,f[i]);
cout<<ans;
return ;
}

 整体二分部分

整体二分本质和CDQ差不多,都是基于分治的算法,实现起来比较简单(只要能够深入理解)。

例题(就写过一道)

[ZJOI2013]K大数查询

有N个位置,M个操作。操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少。

这题什么神奇分块树套树的都能过,整体二分也是一个很经典的解法。

我们现在有一个操作和询问混合的序列,我们需要通过一些操作把它分成两个序列,在这两个序列在时间维度上是单调递增的(在原序列中的相对位置不变),但答案的值域分别小了一半,虽然序列并没有严格二分,但答案值域减小了一半,保证了我们分治的复杂度。

考虑我们有一个序列,里面有在l-r内每个位置插入一个c,还有询问一个区间的第K大,目前的值域为-inf---inf.

我们按照时间的顺序把这个序列扫一遍,遇到操作大于等于mid就把这个l---r+1,当我们遇到一个询问时,我们就查l---r有多少数,实际就是查l---r有多少数是大于等于mid,如果已经够K个了,说明我的第K大一定是>=mid的(显然),所以我们把它放到右序列,否则,它就该在左边,同时我们要把K减去我们查询的结果,意思就是说我们已经数了这么多个了。

同理,对于每个操作,如果>=mid就放到右序列,否则就放到左序列。

然后我们完成了一项使命,把答案值域缩小了一半,接下来继续分治就好了。

边界l==r,此时我的所有询问的答案都是这个值。

时间复杂度,分治nlogn,加上线段树维护操作一个log,总复杂度nlog2n。

Code

#include<iostream>
#include<cstdio>
#define N 50009
using namespace std;
long long tr[N<<];
int la[N<<],n,m,ans[N];
struct ef
{
int l,r,tag,id;
long long v;
}a[N],q1[N],q2[N];
inline void pushdown(int cnt,int l1,int l2)
{
tr[cnt<<]+=la[cnt]*l1;tr[cnt<<|]+=la[cnt]*l2;
la[cnt<<]+=la[cnt];la[cnt<<|]+=la[cnt];la[cnt]=;
}
long long query(int cnt,int l,int r,int L,int R)
{
if(l>=L&&r<=R)return tr[cnt];
int mid=(l+r)>>;
if(la[cnt])pushdown(cnt,mid-l+,r-mid);
long long ans=;
if(mid>=L)ans+=query(cnt<<,l,mid,L,R);
if(mid<R)ans+=query(cnt<<|,mid+,r,L,R);
return ans;
}
void add(int cnt,int l,int r,int L,int R,int tag)
{
if(l>=L&&r<=R)
{
tr[cnt]+=(r-l+)*tag;
la[cnt]+=tag;
return;
}
int mid=(l+r)>>;
if(la[cnt])pushdown(cnt,mid-l+,r-mid);
if(mid>=L)add(cnt<<,l,mid,L,R,tag);
if(mid<R)add(cnt<<|,mid+,r,L,R,tag);
tr[cnt]=tr[cnt<<]+tr[cnt<<|];
}
void solve(int st,int en,int l,int r)
{
if(l==r)
{
for(int i=st;i<=en;++i)
if(a[i].tag==)ans[a[i].id]=l;
return;
}
int mid=(l+r)>>,ll=,rr=;
for(int i=st;i<=en;++i)
if(a[i].tag==)
{
if(a[i].v<=mid)q1[++ll]=a[i];
else
{
add(,,n,a[i].l,a[i].r,);
q2[++rr]=a[i];
}
}
else
{
long long val=query(,,n,a[i].l,a[i].r);
if(val>=a[i].v)
{
q2[++rr]=a[i];
}
else a[i].v-=val,q1[++ll]=a[i];
}
for(int i=st;i<=en;++i)
if(a[i].tag==&&a[i].v>mid)add(,,n,a[i].l,a[i].r,-);
for(int i=;i<=ll;++i)a[st+i-]=q1[i];
for(int i=;i<=rr;++i)a[st+ll+i-]=q2[i];
solve(st,st+ll-,l,mid);solve(st+ll,en,mid+,r);
}
int main()
{
scanf("%d%d",&n,&m);int tot=;
for(int i=;i<=m;++i)
{
scanf("%d%d%d%lld",&a[i].tag,&a[i].l,&a[i].r,&a[i].v);
if(a[i].tag==)a[i].id=++tot;
}
solve(,m,-n,n);
for(int i=;i<=tot;++i)printf("%d\n",ans[i]);
return ;
}

Dynamic Rankings

给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]……a[j]中第k小的数是多少(1≤k≤j-i+1),并且,你可以改变一些a[i]的值,改变后,程序还能针对改变后的a继续回答上面的问题。你需要编一个这样的程序,从输入文件中读入序列a,然后读入一系列的指令,包括询问指令和修改指令。

对于每一个询问指令,你必须输出正确的回答。

Solution

区间待修第k大,经典整体二分题。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#define N 100002
using namespace std;
int tr[N<<],ji[N<<],top,tot,x,y,ans[N],n,m,aa,num[N];
char c;
inline void add(int x,int y){while(x<=top)tr[x]+=y,x+=x&-x;}
inline int query(int x){int ans=;while(x)ans+=tr[x],x-=x&-x;return ans;}
struct sd{
int x,y,tag,l,r,k;
}q[N*],a[N*],b[N*];
void solve(int l,int r,int L,int R){
if(L==R)
{
for(int i=l;i<=r;++i)
if(q[i].l)ans[q[i].tag]=L;
return;
}
int mid=(L+R)>>,l1=,l2=;
for(int i=l;i<=r;++i){
if(q[i].x!=){
if(q[i].y<=mid)add(q[i].x,q[i].tag),a[++l1]=q[i];
else b[++l2]=q[i];
}
else{
int tmp=query(q[i].r)-query(q[i].l-);
if(tmp>=q[i].k)a[++l1]=q[i];
else q[i].k-=tmp,b[++l2]=q[i];
}
}
for(int i=;i<=l1;++i)if(a[i].x)add(a[i].x,-a[i].tag);
for(int i=l;i<=l+l1-;++i)q[i]=a[i-l+];
for(int i=l+l1;i<=r;++i)q[i]=b[i-l-l1+];
solve(l,l+l1-,L,mid);solve(l+l1,r,mid+,R);
}
char ge(){
char c=getchar();
while(c!='Q'&&c!='C')c=getchar();
return c;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=;i<=n;++i){
scanf("%d",&x);ji[++top]=x;
q[++tot].x=i;q[tot].y=x;q[tot].tag=;
num[i]=x;
}
for(int i=;i<=m;++i){
c=ge();
if(c=='Q'){
++tot;
scanf("%d%d%d",&q[tot].l,&q[tot].r,&q[tot].k);
q[tot].tag=++aa;
}
else{
scanf("%d%d",&x,&y);
q[++tot].x=x;q[tot].y=num[x];q[tot].tag=-;
q[++tot].x=x;q[tot].y=y;q[tot].tag=;
num[x]=y;//care!!!!!
ji[++top]=y;
}
}
sort(ji+,ji+top+);
top=unique(ji+,ji+top+)-ji-;
for(int i=;i<=tot;++i)
if(q[i].y)q[i].y=lower_bound(ji+,ji+top+,q[i].y)-ji;
solve(,tot,,top);
for(int i=;i<=aa;++i)printf("%d\n",ji[ans[i]]);
return ;
}

CDQ分治与整体二分学习笔记的更多相关文章

  1. 一篇自己都看不懂的CDQ分治&整体二分学习笔记

    作为一个永不咕咕咕的博主,我来更笔记辣qaq CDQ分治 CDQ分治的思想还是比较简单的.它的基本流程是: \(1.\)将所有修改操作和查询操作按照时间顺序并在一起,形成一段序列.显然,会影响查询操作 ...

  2. [学习笔记]CDQ分治和整体二分

    序言 \(CDQ\) 分治和整体二分都是基于分治的思想,把复杂的问题拆分成许多可以简单求的解子问题.但是这两种算法必须离线处理,不能解决一些强制在线的题目.不过如果题目允许离线的话,这两种算法能把在线 ...

  3. CDQ分治与整体二分小结

    前言 这是一波强行总结. 下面是一波瞎比比. 这几天做了几道CDQ/整体二分,感觉自己做题速度好慢啊. 很多很显然的东西都看不出来 分治分不出来 打不出来 调不对 上午下午晚上的效率完全不一样啊. 完 ...

  4. 技巧专题3(cdq分治、整体二分等)

    cdq分治与整体二分 cdq来源于2008年国家集训队作业陈丹琦(雅礼巨佬),用一个log的代价完成从静态到动态(很多时候是减少时间那一维的). 对于一个时间段[L, R],我们取mid = (L + ...

  5. wqs二分 学习笔记

    wqs二分学习笔记 wqs二分适用题目及理论分析 wqs二分可以用来解决这类题目: 给你一个强制要求,例如必须\(n\)条白边,或者划分成\(n\)段之类的,然后让你求出最大(小)值.但是需要满足图像 ...

  6. Cdq分治整体二分学习记录

    这点东西前前后后拖了好几个星期才学会……还是自己太菜啊. Cdq分治的思想是:把问题序列分割成左右两个,先单独处理左边,再处理左边对右边的影响,再单独处理右边.这样可以消去数据结构上的一个log,降低 ...

  7. CDQ分治&整体二分学习个人小结

    目录 小结 CDQ分治 二维LIS 第一道裸题 bzoj1176 Mokia bzoj3262 陌上花开 bzoj 1790 矩形藏宝地 hdu5126四维偏序 P3157 [CQOI2011]动态逆 ...

  8. cdq分治入门and持续学习orz

    感觉cdq分治是一个很有趣的算法 能将很多需要套数据结构的题通过离线来做 目前的一些微小的理解 在一般情况下 就像求三维偏序xyz 就可以先对x排序 然后分治 1 cdq_x(L,M) ; 2 提取出 ...

  9. Codeforces 1039D You Are Given a Tree [根号分治,整体二分,贪心]

    洛谷 Codeforces 根号分治真是妙啊. 思路 考虑对于单独的一个\(k\)如何计算答案. 与"赛道修建"非常相似,但那题要求边,这题要求点,所以更加简单. 在每一个点贪心地 ...

随机推荐

  1. 3 The simple past

    1 许多动词通过在原型之后添加-ed 构成一般过去式. 其他动词有不规则的过去式,使用一般过去式的时间词语出现在句首或者句尾 The company grew from 400 to 5,000 pe ...

  2. [转帖]windows7/windows NT介绍

    windows7/windows NT介绍 原文应该是IT168发布的 但是一直没找到 感觉看了之后 明白了很多 技术都是互相融合的 没有严格意义上的对立直说.   Windows 7/Windows ...

  3. Windows 机器上面同时安装mysql5.6 和 mysql5.7 的方法

    1. 自己遇到的两个坑: . mysql 登录的时候 需要使用-P 来指定端口号 不然默认走 呢 . mysql 5.6 和 mysql 5.7 更改用户密码的命令不一样.. 我这边浪费了很长时间: ...

  4. day 7-12 数据库的基本操作和存储引擎

    一. 储备知识 数据库服务器:一台高性能计算机 数据库管理系统:mysql(mssql等),是一个软件 数据库:db1(student_db),是一个文件夹 表:studen_info 是一个文件 记 ...

  5. windows环境下protobuf的java操作{编译,序列化,反序列化}

    google protocol buffer的使用和原理 概况: Protocol Buffers(也就是protobuf)是谷歌的语言中立的.平台中立的.可扩展的用于序列化结构化的数据: windo ...

  6. java 中Excel的导入导出

    部分转发原作者https://www.cnblogs.com/qdhxhz/p/8137282.html雨点的名字  的内容 java代码中的导入导出 首先在d盘创建一个xlsx文件,然后再进行一系列 ...

  7. Python:matplotlib绘制线条图

    线型图是学习matplotlib绘图的最基础案例.我们来看看具体过程:  下面我们将两条曲线绘制到一个图形里:   可以看到这种方式下,两个线条共用一个坐标轴,并且自动区分颜色. plot方法的核心是 ...

  8. Lodop打印设计矩形重合预览线条变粗

    LODOP中的打印设计是辅助进行开发的,实际打印效果应以预览为准,很多效果都是在设计界面显示不出来,或设计和预览界面有差异.例如add_print_text文本的字间距.行间距,旋转,还有允许标点溢出 ...

  9. 阿里云 ECS 安全组

    以前在案例云买的ECS我一般都是 连上 ssh,然后把网站文件拿上去 ,安装好需要的环境 然后就可以顺利的打开网站了,这次帮一个朋友买的阿里云ECS让我蒙了, 一切都准备好了 网站打不开 防火墙也检查 ...

  10. faster rcnn训练自己的数据集

    采用Pascal VOC数据集的组织结构,来构建自己的数据集,这种方法是faster rcnn最便捷的训练方式