◆学时·IX◆ 整体二分

至于我怎么了解到这个算法的……只是因为发现一道题,明显的二分查找,但是时间会爆炸,被逼无奈搜题解……然后就发现了一些东西QwQ


◇ 算法概述

整体二分大概是把BFS与二分查找完美结合了。

它针对一种可以用二分查找(直接查找答案),但是询问很多,对于每一个询问都做二分查找会超时的问题。

由于多次询问,那么问题的答案很可能在二分的区间中比较分散,那么我们可以用BFS枚举出二分将会产生的所有区间的情况……类似于线段树:

最初区间为 [1,m] 然后从该区间再拓展出 [1,mid][mid+1,m] …… 最后拓展到左端点等于右端点就可以得到答案。

那么我们知道——对于一个问题,它的解一定是区间的一个点——而一个区间,可能包含这个点。整体二分的思想,就是利用BFS可以存储数据的特点,用结构体NODE作为queue的类型。而每一个NODE节点描述了一个区间 [l,r] ,以及在这个区间里包含哪些问题的答案(用vector来存储)。BFS可以拓展到其他的节点,而这里我们的二分查找就类似于线段树,将原来的区间A一分为二为Lef,Rig,同时将A所包含的问题按它的答案的位置分配到Lef,Rig中,就完成了扩展。至于怎么分配问题……就看题目了。

当然也有可能在二分查找拓展出的一个区间中不包含任何一个问题的答案,这时候我们可以剪枝,因为这个区间无论怎么细分都无法得到任何一个问题的答案。

有一些题是不能用整体二分的,具体而言,能用整体二分的题目满足以下条件:

  • 满足二分查找的单调性;
  • 如果在区间[l,r]中包含某问题的解,则在[l,mid]或[mid+1,r]中也一定包含它的解;
  • 问题的答案不一定是单独一个点,可以是多个点,分配问题时需要判断;

{下面就来看看到底哪些题目可以用整体二分吧(我目前只找到了一个,以后会继续更新( ̄▽ ̄)")}

◇ 例题简析

【Atcoder 1998】Stamp Rally

【题意】

给出一个无向图(n点m边),按照边输入的顺序编号为1~m。给出q个询问,每个询问[a,b,c]。对于每个询问定义一个花费 cst 为从a,b出发,到达恰好c个点所经过的最大边,求出对于每个询问的最小cst。

【样例&解析】

无向图:

分别从2,3出发,到达3个点,则经过2->3,经过最大边(cst)最小为1;1,3出发到达4个点,则经过3->2,1->4,最大边最小为5……

【解析】

如果这道题只有一次询问的话,很容易想到二分查找。查找答案,即最大边编号,这样就相当于限制了能够经过哪些边。这是满足单调性的,也就是说当限制的最大边越大,就意味着能够通过的边就越多,则从给定点a,b出发能够到达的点一定会更多(或者不变)。那么我们就可以在[1,m]中二分答案,再每次Check从a,b能够到达的点的数量num,如果num>=c则缩小为左区间,否则缩小为右区间。

但是分析一下时间复杂度——二分查找O(logn),Check最坏情况O(n),总共q次询问……那么最后时间复杂度就是O(qnlogn),显然是爆炸的(>﹏<)。

虽然整体二分有一个非常奇怪的巨大的常数(STL的容器vector之类的总是有些奇怪的常数),但是总体来说运行速度还是快得多。

如何整体二分?我们需要定义一个结构体来存储一个二分状态——当前区间的左右端点l,r,以及答案在区间[l,r]内的询问(这里二分的就是询问的答案)。存储询问我又用了一个结构体,存储了询问的a,b点和c值,以及询问的编号,因为要离线处理(虽然说这个结构体没什么必要,但是好表示一些)。

struct PROBLEM {
int a,b,siz,id;
PROBLEM() {}
PROBLEM(int _a,int _b,int _siz,int _id):a(_a),b(_b),siz(_siz),id(_id) {}
};
struct NODE {
int l,r;
vector<PROBLEM> pro;
} beg;

由于题目保证所有询问都有解,所以所有问题的解应该都在[1,m]内,所以初始节点beg就是[1,m],然后包含的询问就是所有询问。

从这个节点开始拓展。扩展节点的方法是:

  1. 找到mid=(l+1)/2。
  2. 判断当限制mid为可以经过的最大边时,从a,b出发能够到达多少个点,记为num。
  3. 枚举包含在当前区间内的询问pro[i],若 num<pro[i].c 则分配给 [mid+1,r](mid小于询问答案);否则分配给 [l,mid](mid大于等于询问答案)。
  4. 当 l=r 时,则已经锁定到一个点,可以得出答案;当前包含在区间内的问题的答案为 l(r是一样的)。
  5. (可有可无,反正我没写 (●'◡'●))如果当前区间内不包含任何一个问题的解,剪掉。

基本框架就是这样,但是比较困难的就是求从a,b点出发,经过小于等于mid的边能够到达的点的个数了。由于这就是求a,b所在连通块的大小(删除大于mid的边),我借鉴了一种维护并查集的方法……首先对并查集进行一些改动——把并查集看成一棵树,然后维护一个以i为根的子树的大小sz[i]。这就意味着我们不能随意地将两个并查集合并,要遵循一定的顺序:

  1. 重置/初始化 并查集 Clear() —— 每个点都单独形成一个集合,同时也单独形成一棵树(只有一个节点),所以fa[i]=i,sz[i]=1;
  2. 查找祖先,即树根,Find() 没什么变化;
  3. 合并两个并查集,假设我们要合并u所在并查集和v所在并查集,则找到u,v分别所在树的根 preu,prev,如果preu=prev说明它们本来就属于一个集合,不需要合并,否则保留prev为树根,将preu连到prev上,作为一个儿子,以prev为根节点的树大小增加sz[u];

Okey!这样我们就可以统计a,b所在连通块即a,b所在树的大小了——当a,b属于同一棵树时,大小就是sz[Find(a)],否则就是 sz[Find(a)]+sz[Find(b)] 。但是对于每一个mid,并查集是不一样的……我们就需要每次清空并查集再重新建立,除非你用可持久化支持删边(当然我不愿意)。时间复杂度依旧很悬(>︿<)。那么就可以想一想,能否利用上一次计算出的并查集再加边形成现在需要的状态?我们可以记录一个las表示上一次并查集加入的边是 1~las 。如果las<mid,那么我们就可以把 las+1~mid 的边加入并查集,而不是重构并查集;但是如果las>mid,就只能重构了。

具体有一些细节上的优化,比如push状态时先左儿子,再右儿子,进行的重构操作会少一些……

(有什么问题我没有讲清楚?可以在文末的邮箱处问我(/▽\))

【源代码】

/*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int N=int(1e5);
int n,m,q;
int fa[N+5],sz[N+5],ans[N+5];
void Clear() {
for(int i=1; i<=n; i++)
fa[i]=i,sz[i]=1;
}
int Find(int u) {
if(fa[u]==u) return u;
else return fa[u]=Find(fa[u]);
}
void Link(int u,int v) {
int pre_u=Find(u),pre_v=Find(v);
if(pre_u==pre_v) return;
fa[pre_u]=pre_v;
sz[pre_v]+=sz[pre_u];
}
pair<int,int> edg[N+5];
struct PROBLEM {
int a,b,siz,id;
PROBLEM() {}
PROBLEM(int _a,int _b,int _siz,int _id):a(_a),b(_b),siz(_siz),id(_id) {}
};
struct NODE {
int l,r;
vector<PROBLEM> pro;
} beg;
int main() {
scanf("%d%d",&n,&m);
for(int i=1; i<=m; i++) {
int u,v;
scanf("%d%d",&u,&v);
edg[i]=make_pair(u,v);
}
scanf("%d",&q);
for(int i=0; i<q; i++) {
int a,b,siz;
scanf("%d%d%d",&a,&b,&siz);
beg.pro.push_back(PROBLEM(a,b,siz,i));
}
beg.l=1;
beg.r=m;
queue<NODE> que;
que.push(beg);
int las=0;
Clear();
while(!que.empty()) {
NODE pre=que.front(),R,L;
que.pop();
if(pre.l==pre.r) {
for(int i=0; i<pre.pro.size(); i++)
ans[pre.pro[i].id]=pre.l;
continue;
}
int mid=(pre.l+pre.r)>>1;
if(las>mid) {
las=0;
Clear();
}
while(las<mid) {
las++;
Link(edg[las].first,edg[las].second);
}
for(int i=0; i<pre.pro.size(); i++) {
int a=pre.pro[i].a,b=pre.pro[i].b,siz=pre.pro[i].siz,id=pre.pro[i].id;
int prea=Find(a),preb=Find(b),tot;
if(prea==preb) tot=sz[prea];
else tot=sz[prea]+sz[preb];
if(tot<siz) R.pro.push_back(PROBLEM(a,b,siz,id));
else L.pro.push_back(PROBLEM(a,b,siz,id));
}
L.l=pre.l;
L.r=mid;
R.l=mid+1;
R.r=pre.r;
que.push(L);
que.push(R);
}
for(int i=0; i<q; i++)
printf("%d\n",ans[i]);
return 0;
}

  


The End

Thanks for reading!

- Lucky_Glass

(Tab:如果我有没讲清楚的地方可以直接在邮箱lucky_glass@foxmail.com email我,在周末我会尽量解答并完善博客~

【学时总结】◆学时·IX◆ 整体二分的更多相关文章

  1. 【XSY2720】区间第k小 整体二分 可持久化线段树

    题目描述 给你你个序列,每次求区间第\(k\)小的数. 本题中,如果一个数在询问区间中出现了超过\(w\)次,那么就把这个数视为\(n\). 强制在线. \(n\leq 100000,a_i<n ...

  2. 算法笔记--CDQ分治 && 整体二分

    参考:https://www.luogu.org/blog/Owencodeisking/post-xue-xi-bi-ji-cdq-fen-zhi-hu-zheng-ti-er-fen 前置技能:树 ...

  3. 整体二分QAQ

    POJ 2104 K-th Number 时空隧道 题意: 给出一个序列,每次查询区间第k小 分析: 整体二分入门题? 代码: #include<algorithm> #include&l ...

  4. BZOJ 3110 [Zjoi2013]K大数查询 ——整体二分

    [题目分析] 整体二分显而易见. 自己YY了一下用树状数组区间修改,区间查询的操作. 又因为一个字母调了一下午. 貌似树状数组并不需要清空,可以用一个指针来维护,可以少一个log 懒得写了. [代码] ...

  5. [bzoj1901][zoj2112][Dynamic Rankings] (整体二分+树状数组 or 动态开点线段树 or 主席树)

    Dynamic Rankings Time Limit: 10 Seconds      Memory Limit: 32768 KB The Company Dynamic Rankings has ...

  6. bzoj 2527: [Poi2011]Meteors 整体二分

    给每个国家建一个链表,这样分治过程中的复杂度就和序列长度线形相关了,无脑套整体二分就可以. (最坑的地方是如果所有位置都是一个国家,那么它的样本个数会爆longlong!!被这个坑了一次,大于p[i] ...

  7. 【BZOJ-2527】Meteors 整体二分 + 树状数组

    2527: [Poi2011]Meteors Time Limit: 60 Sec  Memory Limit: 128 MBSubmit: 831  Solved: 306[Submit][Stat ...

  8. BZOJ 1901 Zju2112 Dynamic Rankings ——整体二分

    [题目分析] 上次用树状数组套主席树做的,这次用整体二分去水. 把所有的查询的结果一起进行二分,思路很好. [代码] #include <cstdio> #include <cstr ...

  9. BZOJ 1901 & 整体二分

    题意: 带修改的区间第K小. SOL: 看了很久很久很久很久的整体二分,网上的各种题解也不是很多,也一直很不了解所谓的"贡献","将询问一起递归"是什么意思.. ...

随机推荐

  1. win10下MySQL 5.7.20解压版安装步骤

    1.从官网下载MySQL5.7.20解压版64位:https://dev.mysql.com/downloads/file/?id=473309. 2.解压(我的解压路径为:E:\mysql-5.7. ...

  2. html-超链接标签

    链接资源 - <a href="01-hello.html">只是一个超链接1</a> ** href:链接的资源的地址 ** target:设置打开的方式 ...

  3. cf547D. Mike and Fish(欧拉回路)

    题意 题目链接 Sol 说实话这题我到现在都不知道咋A的. 考试的时候是对任意相邻点之间连边,然后一分没有 然后改成每两个之间连一条边就A了.. 按说是可以过掉任意坐标上的点都是偶数的数据啊.. #i ...

  4. 分享一个好东西(一天精通MongoDB数据库)

    https://pan.baidu.com/s/1o7V5e8U 总共几个小时的视频,看了之后醍醐灌顶.分享出来.

  5. Centos6配置samba服务器并批量添加用户和文件夹

    一.需求 局域网内有若干用户,所有用户访问一个共享目录 每个用户在共享目录里有自己的文件夹 每个用户都可以读取其他人的文件夹 每个用户只能对自己的文件夹有写入权限 所有用户都属于filesgroup组 ...

  6. css 字体样式设置大全

    css样式大全(整理版)   字体属性:(font) 大小 {font-size: x-large;}(特大) xx-small;(极小) 一般中文用不到,只要用数值就可以,单位:PX.PD 样式 { ...

  7. 小于12px的字体大小在Chrome中不起作用

    今天遇见一个小问题,让人挺郁闷的,在Chrome浏览器中无法把字体变成12px以下.网上搜索以下,发现无论中文英文数字在网页中CSS设置小于12px后各大浏览器均支持,在谷歌chrome浏览器不支持解 ...

  8. May 21st 2017 Week 21st Sunday

    The smallest deed is better than the greatest intention. 最微小的行动胜过最伟大的打算. Several years ago, just aft ...

  9. May 16th 2017 Week 20th Tuesday

    The most fearful enemy is not having a firm conviction. 最可怕的敌人,就是没有坚强的信念. A firm conviction or belie ...

  10. Scrum _GoodJob

    作为长大的大三老腊肉,我们已经在长大生活了两年多,对于什么是长大人最想完善的校园需求.最想拥有的校园服务媒介也有了更加深切的体会. 于是,GoodJob小团队blingbling闪现啦!! GoodJ ...