整体二分浅谈

整体二分浅谈

一、前置知识

  在学习整体二分之前,要学会二分,以及二分的分治思想。

二、整体二分浅谈及例题

  例题:bzoj2527: [Poi2011]Meteors

  对于这道题是整体二分的经典例题,我们先抛开整体二分,思考二分怎么做。对于一个询问,因为答案有单调性,如果$x$时刻为最小可以时刻,则比$x$小的时刻都不可以,比$x$大的时刻都可以,所以我们可以进行二分答案,并加以验证。先不说怎样验证,就单是时间复杂度就不能接受,$O(nmlog_2^n)$。

  如果一个一个进行二分时间复杂度不允许,且这些询问不是强制在线的,我们不妨整体进行二分,我们把所有询问放在一起进行二分。我们设计一个函数$solve(l,r,x,y)$,表示当前询问序列$[x,y]$的答案在当前答案$[l,r]$区间。有一个问题,就是为什么答案在答案区间$[l,r]$的所有询问会连接在一起,在询问序列的连续一段呢?这个问题放在后面,先置之不理。我们思考,怎样进行二分。

  二分答案的思想是取出当前答案区间的中间值进行验证,如果比答案小,则让答案的区间的左端点为中间值加一,反之让答案的右端点为中间值。按照二分答案的思想,我们也进行中间值验证。看例题,我们思考怎么验证。

  对于当前答案区间$[l,r]$,我们把第$[l,mid]$场流星雨全部落下,看在当前答案区间$[l,r]$所属的所有询问是否在第$[l,mid]$场流星雨下过之后已经收集足够的陨石,如果当前询问已经收集够,我们把它归为答案区间$[l,mid ]$中,反之我们把它归为答案区间$[mid+1,r]$中,并且对于归为答案区间$[mid+1,r]$的询问我们需要进行修改,对于其希望要收集的陨石数要减去$[l,mid]$场流星雨的陨石总数,此处理解一下。

  现在解决一下上面留下的问题,我们怎么能将答案都在$[x,y]$区间的所有询问都放在一起呢?我们对于每一次划分,都将这些询问进行拷贝,并且修改,然后重新按左右排布,这样我们就能让这些答案在同一区间的询问在一起了。

  我们分析一下时间复杂度:我们运用线段树的思想进行分析,我们一共有$log_2^{r-l+1}$层,在这个式子中的$r$表示答案可能到达的最大值,反之$l$表示的就是答案可能到达的最小值,在本题中,我们的$r=n,l=1$,但是下方的代码最开始的传参为$r=n+1$,这表示前$n$场流星雨都不能满足这个询问,所以最后落在$n+1$的所有询问表示不能在所有$n$个流行雨中的到满足,故输出$-1$。每一层中我们运用线段树的思想,知道遍历每一层的所有流星雨,一共是线性的时间复杂度,并且每一层正正好好摊分所有$m$个询问,在每一层中我们每一个询问和流星雨都会运用树状数组,所以总的时间复杂度是$O((n+m)log_2^{n}log_2^{r-l+1})$。

#include <cstdio>
#include <algorithm>
using namespace std;
#define N 300010
struct Per {int head,id;long long need;}per[N],per_[N<<1];
int n,m,k,L[N],R[N];long long A[N];int ans[N],nxt[N],to[N],idx;long long tmp[N<<1];
void add(int a,int b) {nxt[++idx]=per[a].head,to[idx]=b,per[a].head=idx;}
void change(int x,long long y) {while(x<=2*m) tmp[x]+=y,x+=x&-x;}
long long find(int x) {long long sum=0;while(x) sum+=tmp[x],x-=x&-x;return sum;}
void solve(int l,int r,int x,int y)
{
    if(l==r) {for(int i=x;i<=y;i++) ans[per[i].id]=l;return;}
    int mid=(l+r)>>1,tl=0,tr=n;
    for(int i=l;i<=mid;i++) change(L[i],A[i]),change(R[i]+1,-A[i]);
    for(int i=x;i<=y;i++)
    {
        long long tmp1=0;
        for(int j=per[i].head;j&&tmp1<=per[i].need;j=nxt[j])
            tmp1+=find(to[j]+m)+find(to[j]);
        if(tmp1>=per[i].need) per_[++tl]=per[i];
        else per_[++tr]=per[i],per_[tr].need-=tmp1;
    }
    for(int i=l;i<=mid;i++) change(L[i],-A[i]),change(R[i]+1,A[i]);
    for(int i=1;i<=tl;i++) per[x+i-1]=per_[i];
    for(int i=n+1;i<=tr;i++) per[x+tl+i-n-1]=per_[i];
    solve(l,mid,x,x+tl-1),solve(mid+1,r,y-tr+n+1,y);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1,a;i<=m;i++) scanf("%d",&a),add(a,i);
    for(int i=1;i<=n;i++) scanf("%lld",&per[i].need),per[i].id=i;
    scanf("%d",&k);for(int i=1;i<=k;i++) scanf("%d%d%lld",&L[i],&R[i],&A[i]);
    for(int i=1;i<=k;i++) if(R[i]<L[i]) R[i]+=m; solve(1,k+1,1,n);
    for(int i=1;i<=n;i++) (ans[i]==k+1)?printf("NIE\n"):printf("%d\n",ans[i]);
}

三、习题

  bzoj2161: 布娃娃

  题解:这道题我们可以运用主席树,或是树状数组来解决。但是我们这道题要运用整体二分来解决,我们发现这道和上一道题差不多,也是区间覆盖,但是是找第$k?$大的,同样与上一道题一样,我们每一次进行验证答案的中间值,并进行左右递归,最后处理出答案。

#include <cstdio>
#include <algorithm>
using namespace std;
#define N 300010
#define mod 19921228
int n,num[N],placel[N],placer[N],number[N],ans[N],idx,tmp[N],P[N],C[N],L[N],R[N];
struct Doll {int id,p,l,r,k,val;}doll[N],doll_[N];
char *p1,*p2,buf[100000];
#define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
int rd() {int x=0,f=1; char c=nc(); while(c<48)
    {if(c==‘-‘) f=-1;c=nc();} while(c>47) x=(((x<<2)+x)<<1)+(c^48),c=nc(); return x*f;}
bool cmp(const Doll &a,const Doll &b) {return a.val>b.val;}
void change(int x,int y) {while(x<=idx) tmp[x]+=y,x+=x&-x;}
int find(int x) {int sum=0;while(x) sum+=tmp[x],x-=x&-x;return sum;}
int find_ord(int x) {int l=1,r=idx+1;
    while(l<r) {int mid=(l+r)>>1;(number[mid]>=x)?r=mid:l=mid+1;}return l;}
void solve(int l,int r,int x,int y)
{
    if(l==r) {for(int i=x;i<=y;i++) ans[doll[i].id]=num[l];return;}
    int mid=(l+r)>>1,tl=0,tr=n;
    for(int i=l;i<=mid;i++) change(placel[i],1),change(placer[i]+1,-1);
    for(int i=x;i<=y;i++)
    {
        int tmp1=find(doll[i].p);
        if(tmp1>=doll[i].k) doll_[++tl]=doll[i];
        else doll_[++tr]=doll[i],doll_[tr].k-=tmp1;
    }
    for(int i=l;i<=mid;i++) change(placel[i],-1),change(placer[i]+1,1);
    for(int i=1;i<=tl;i++) doll[x+i-1]=doll_[i];
    for(int i=n+1;i<=tr;i++) doll[x+tl+i-n-1]=doll_[i];
    solve(l,mid,x,x+tl-1),solve(mid+1,r,y-tr+n+1,y);
}
int main()
{
    n=rd();
	int Padd=rd(),Pfirst=rd(),Pmod=rd(),Pprod=rd();
	int Cadd=rd(),Cfirst=rd(),Cmod=rd(),Cprod=rd();
	int Ladd=rd(),Lfirst=rd(),Lmod=rd(),Lprod=rd();
	int Radd=rd(),Rfirst=rd(),Rmod=rd(),Rprod=rd();
	P[1]=Pfirst%Pmod; for(int i=2;i<=n;i++) P[i]=(1ll*P[i-1]*Pprod+Padd+i)%Pmod;
	C[1]=Cfirst%Cmod; for(int i=2;i<=n;i++) C[i]=(1ll*C[i-1]*Cprod+Cadd+i)%Cmod;
	L[1]=Lfirst%Lmod; for(int i=2;i<=n;i++) L[i]=(1ll*L[i-1]*Lprod+Ladd+i)%Lmod;
	R[1]=Rfirst%Rmod; for(int i=2;i<=n;i++) R[i]=(1ll*R[i-1]*Rprod+Radd+i)%Rmod;
    for(int i=1;i<=n;i++) if(L[i]>R[i]) swap(L[i],R[i]);
    for(int i=1;i<=n;i++) doll[i].l=L[i],doll[i].r=R[i],
        doll[i].p=P[i],doll[i].val=C[i],doll[i].k=i,doll[i].id=i;
    for(int i=1;i<=n;i++) number[++idx]=doll[i].p,number[++idx]=doll[i].l,number[++idx]=doll[i].r;
    sort(number+1,number+idx+1);
    for(int i=1;i<=n;i++)
        doll[i].p=find_ord(doll[i].p),doll[i].l=find_ord(doll[i].l),doll[i].r=find_ord(doll[i].r);
    sort(doll+1,doll+n+1,cmp);
    for(int i=1;i<=n;i++) num[i]=doll[i].val,doll[i].val=i,placel[i]=doll[i].l,placer[i]=doll[i].r;
    solve(1,n+1,1,n); for(int i=1;i<=n;i++) (ans[i]+=ans[i-1])%=mod; printf("%d\n",ans[n]);
}

原文地址:https://www.cnblogs.com/yangsongyi/p/10353636.html

时间: 2024-10-28 21:58:46

整体二分浅谈的相关文章

浅谈自然语言处理基础(下)

命名实体识别 命名实体的提出源自信息抽取问题,即从报章等非结构化文本中抽取关于公司活动和国防相关活动的结构化信息,而人名.地名.组织机构名.时间和数字表达式结构化信息的关键内容,所以需要从文本中去识别这些实体指称及其类别,即命名实体识别和分类. 21世纪以后,基于大规模语料库的统计方法成为自然语言处理的主流,以下是基于统计模型的命名实体识别方法归纳: 基于CRF的命名实体识别方法 基于CRF的命名实体识别方法简便易行,而且可以获得较好的性能,广泛地应用于人名.地名和组织机构等各种类型命名实体的识

整体二分初探 两类区间第K大问题 poj2104 &amp; hdu5412

看到好多讲解都把整体二分和$CDQ$分治放到一起讲 不过自己目前还没学会$CDQ$分治 就单独谈谈整体二分好了 先推荐一下$XHR$的 <浅谈数据结构题的几个非经典解法> 整体二分在当中有较为详细的讲解 先来说一下静态第$K$小的整体二分解法 $(POJ2104)$ 题目链接:http://poj.org/problem?id=2104 所谓整体二分 就是利用所有的询问相互独立而把它们$($此题没有修改操作$)$通过二分把它们分治进行处理 不妨先来考虑下一个简单易懂的$O(NlogS)$的排序

【cdq分治】cdq分治与整体二分学习笔记Part1.整体二分

之所以把cdq分治和整体二分放在一起学习,是因为他们两个实在太像了-不管是做法还是代码- 感觉整体二分可能会比cdq分治稍微简单那么一点点?所以先学整体二分.(感觉他们的区别在于整体二分是对每个操作二分答案,cdq是分治了操作序列) 整体二分是对答案进行二分,其具体操作如下: (比如以ZJOJ2013K大数查询为例) 具体过程 Step1.从(L,R)二分答案.mid=(L+R)>>1,用线段树维护原序列中(a,b)位置比mid大的数有多少个,同时记录对序列的操作分别是什么操作. Step2.

从《楼房重建》出发浅谈一类使用线段树维护前缀最大值的算法

首先需要申明的是,真的是浅谈,因为我对这个算法的认识还是非常低的. 既然是从<楼房重建>出发,那么当然是先看看这道题: [清华集训2013]楼房重建 bzoj 链接 题意简述: 有 \(n\) 栋楼,第 \(i\) 栋的高度为 \(H_i\),也就是说第 \(i\) 栋楼可以抽象成一条两端点为 \((i, 0)\) 和 \((i, H_i)\) 的线段. 初始时 \(H_i\) 均为 \(0\),要支持动态修改单点的 \(H_i\). 每次询问从 \(O(0, 0)\) 点可以看到多少栋楼房.

浅谈HTML5单页面架构(一)——requirejs + angular + angular-route

本文转载自:http://www.cnblogs.com/kenkofox/p/4643760.html 心血来潮,打算结合实际开发的经验,浅谈一下HTML5单页面App或网页的架构. 众所周知,现在移动Webapp越来越多,例如天猫.京东.国美这些都是很好的例子.而在Webapp中,又要数单页面架构体验最好,更像原生app.简单来说,单页面App不需要频繁切换网页,可以局部刷新,整个加载流畅度会好很多. 废话就不多说了,直接到正题吧,浅谈一下我自己理解的几种单页面架构: 1.requirejs

浅谈结对编程

浅谈结对编程 结对编程 结对编程,是一种敏捷软件开发的方法,极限编程的组成部分.结对编程技术是指两位程序员肩并肩地坐在同一台电脑前合作完成同一个设计.同一个算法.同一段代码或同一组测试.一人充当“执行”角色,只负责编程.另外则负责“观察者”(或“导航”),检测bug和把控整体设计.两个程序员具有相同的缺点和盲点的可能性很小,所以当我们采用结对编程的时候会获得一个强大的解决方案.而这个解决方案恰恰是其它软件工程方法学中所没有的. 由于自己长时间都习惯了一个人编程,所以在这次结对编程的初期在做项目的

图标字体化浅谈[转]

在做手机端Web App项目中,经常会遇到小图标在手机上显示比较模糊的问题,经过实践发现了一种比较好的解决方案,图标字体化.在微社区项目中,有很多小的Icon(图标),如分享.回复.赞.返回.话题.访问.箭头等,这些Icon(图标)一般都是纯色的.开始制作时考虑用双倍大小的Sprite图,通过CSS样式设置只显示二分之一尺寸,这样在Retina屏上显示的大小是正常的,一旦放大屏幕后图标又变得模糊不清,测试的效果不是很理想,后来又考虑多套图标适配方案.SVG矢量图等,都因为种种原因放弃掉了(如多套

浅谈算法和数据结构

: 一 栈和队列 http://www.cnblogs.com/yangecnu/p/Introduction-Stack-and-Queue.html 最近晚上在家里看Algorithems,4th Edition,我买的英文版,觉得这本书写的比较浅显易懂,而且“图码并茂”,趁着这次机会打算好好学习做做笔记,这样也会印象深刻,这也是写这一系列文章的原因.另外普林斯顿大学在Coursera 上也有这本书同步的公开课,还有另外一门算法分析课,这门课程的作者也是这本书的作者,两门课都挺不错的. 计算

浅谈软件项目的需求管理

软件项目区别于其它项目的最显著的特征是其不可见性,它不像硬件购销.建筑工程,都是实实在在可见的东西.而软件项目在系统交付之前很长一段时间,客户是无法感知自己想要的系统究竟是什么样子.因此,需求管理就显得十分重要,据相关统计数据分析,软件项目90%以上失败的原因都在于没有重视需求或者需求管理方面做的不到位导致的. 需求管理作为软件项目管理的一个重要内容,贯穿项目实施的全生命周期.俗话说:万事开头难.需求作为软件开发的第一个环节,其重要性不言而喻.市面上关于需求管理的相关理论和书籍很多,但多数停留在