CDQ分治与整体二分学习笔记
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分治与整体二分学习笔记的更多相关文章
- 一篇自己都看不懂的CDQ分治&整体二分学习笔记
作为一个永不咕咕咕的博主,我来更笔记辣qaq CDQ分治 CDQ分治的思想还是比较简单的.它的基本流程是: \(1.\)将所有修改操作和查询操作按照时间顺序并在一起,形成一段序列.显然,会影响查询操作 ...
- [学习笔记]CDQ分治和整体二分
序言 \(CDQ\) 分治和整体二分都是基于分治的思想,把复杂的问题拆分成许多可以简单求的解子问题.但是这两种算法必须离线处理,不能解决一些强制在线的题目.不过如果题目允许离线的话,这两种算法能把在线 ...
- CDQ分治与整体二分小结
前言 这是一波强行总结. 下面是一波瞎比比. 这几天做了几道CDQ/整体二分,感觉自己做题速度好慢啊. 很多很显然的东西都看不出来 分治分不出来 打不出来 调不对 上午下午晚上的效率完全不一样啊. 完 ...
- 技巧专题3(cdq分治、整体二分等)
cdq分治与整体二分 cdq来源于2008年国家集训队作业陈丹琦(雅礼巨佬),用一个log的代价完成从静态到动态(很多时候是减少时间那一维的). 对于一个时间段[L, R],我们取mid = (L + ...
- wqs二分 学习笔记
wqs二分学习笔记 wqs二分适用题目及理论分析 wqs二分可以用来解决这类题目: 给你一个强制要求,例如必须\(n\)条白边,或者划分成\(n\)段之类的,然后让你求出最大(小)值.但是需要满足图像 ...
- Cdq分治整体二分学习记录
这点东西前前后后拖了好几个星期才学会……还是自己太菜啊. Cdq分治的思想是:把问题序列分割成左右两个,先单独处理左边,再处理左边对右边的影响,再单独处理右边.这样可以消去数据结构上的一个log,降低 ...
- CDQ分治&整体二分学习个人小结
目录 小结 CDQ分治 二维LIS 第一道裸题 bzoj1176 Mokia bzoj3262 陌上花开 bzoj 1790 矩形藏宝地 hdu5126四维偏序 P3157 [CQOI2011]动态逆 ...
- cdq分治入门and持续学习orz
感觉cdq分治是一个很有趣的算法 能将很多需要套数据结构的题通过离线来做 目前的一些微小的理解 在一般情况下 就像求三维偏序xyz 就可以先对x排序 然后分治 1 cdq_x(L,M) ; 2 提取出 ...
- Codeforces 1039D You Are Given a Tree [根号分治,整体二分,贪心]
洛谷 Codeforces 根号分治真是妙啊. 思路 考虑对于单独的一个\(k\)如何计算答案. 与"赛道修建"非常相似,但那题要求边,这题要求点,所以更加简单. 在每一个点贪心地 ...
随机推荐
- jmeter 连接数据库测试笔记
JDBC 常用mysql和oracal的jar包下载地址.jdbc driver class配置参考我的博客https://www.cnblogs.com/jackzz/p/9998975.html ...
- Javascript与C#对变量的处理方式
先来看一下Javascript的情况(下面所说的基本类型和简单类型是一个意思): Javascript中变量会存在两种情况,一种是基本类型的,一共有五种,有null.Bollean.undefin ...
- Mac上通过iterm 上传文件到服务器
.安装 brew install lrzsz #这里以homebrew方式安装12.脚本 拉取 https://github.com/mmastrac/iterm2-zmodem 两个sh文件,将他们 ...
- [转帖]windows7/windows NT介绍
windows7/windows NT介绍 原文应该是IT168发布的 但是一直没找到 感觉看了之后 明白了很多 技术都是互相融合的 没有严格意义上的对立直说. Windows 7/Windows ...
- springboot的几种启动方式
一:IDE 运行Application这个类的main方法 二:在springboot的应用的根目录下运行mvn spring-boot:run 三:使用mvn install 生成jar后运行 先到 ...
- python之路--类与类之间的关系
类和类之间的关系 在我们的世界中事物和事物之间总会有一些联系. 在面向对象中. 类和类之间也可以产生相关的关系 1. 依赖关系 执行某个动作的时候. 需要xxx来帮助你完成这个操作. 此时的关系是最轻 ...
- ArcGIS 添加 MarkerSymbol 弹出“图形符号无法序列化为 JSON”错误
今天在做一个demo,向自定义图层中添加MarkerSymbol的时候,弹出“图形符号无法序列化为 JSON”错误,之前都没有出现过这个问题,我们首先来看一看我是怎样去添加图层,然后向图层中添加Gra ...
- ServiceLoader详解
系统中用到了ServiceLoader,查了一下: ServiceLoader与ClassLoader是Java中2个即相互区别又相互联系的加载器.JVM利用ClassLoader将类载入内存,这是一 ...
- python设计模式第二十二天【备忘录模式】
1.应用场景 (1)能保存对象的状态,并能够恢复到之前的状态 2.代码实现 #!/usr/bin/env python #! _*_ coding:UTF-8 _*_ class Originator ...
- 导出数据到EXL表格中
项目使用的是SSI框架,通过struts访问到action xml文件: <action name="fabAttributedaochu" class="com. ...