BZOJ 1901 & 整体二分

题意:

  带修改的区间第K小.

SOL:

  看了很久很久很久很久的整体二分,网上的各种题解也不是很多,也一直很不了解所谓的"贡献","将询问一起递归"是什么意思...看了一晚上的代码终于有所领悟...

  什么是整体二分呢,"整体",整体整体什么是整体,整体就是将包括数,修改,询问一起二分,而如何实现呢?我看我能讲多少,那就算多少好了,还是代码更加直观.

  先贴一下XHR大神犇的论文-----整体二分满足的性质(虽然没有完全理解但好像也很有道理的样子):

    

    先讲一下判定答案是什么,我们对于一个询问时,我们可二分答案然后判断这个答案的rank,然后二分即可.在整体二分中,这个二分的值即为判定答案

    1.询问的答案具有可二分性(显然啊...不然怎么叫做二分...)

    2.修改对判定答案的贡献互相独立(比如说这个问题,添加一个比判定答案大,那么它就比判定答案大了,如果在添加一个比判定答案小的数,并不能对上一个修改构成什么影响...)

    3.修改如果对判定答案有贡献,则贡献为以确定的与判定标准无关的值

    4.贡献满足交换律,结合律,具有可加性.

    (3,4的解释还是贴XHR大神的吧...我根本不能怎么表达...

    因为贡献的值与判定标准无关,所以如果我们已经计算过某一些修改对询问的贡献,那么这个贡献永远不会改变------(若一个修改比判定答案大,那么它就比判定答案大了.你大爷永远是你大爷. ) 我们没有必要当判定标准改变时再次计算这部分修改的贡献,只要记录下当前的总贡献,在进一步二分时,直接加上新的贡献即可.

    这部分还是讲不清楚啊...还是看代码比较直观...

    5.题目允许离线算法(怎么感觉画风突变啊...一种忧桑的感觉)

    对于这个题目应该怎么考虑呢...我们将所有的操作添加进一个序列中,然后对所有的询问用同一个二分答案作为判定答案,然后将所有应该增大判定答案的放在一组,减小的放在一组...啊啊啊啊真心说不明白,看代码看代码...加几个注释.

CODE:

/*==========================================================================
# Last modified: 2016-02-25 21:09
# Filename: 1901.cpp
# Description:
==========================================================================*/
#define me AcrossTheSky
#include <cstdio>
#include <cmath>
#include <ctime>
#include <string>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm> 

#include <set>
#include <map>
#include <stack>
#include <queue>
#include <vector> 

#define lowbit(x) (x)&(-x)
#define FOR(i,a,b) for((i)=(a);(i)<=(b);(i)++)
#define FORP(i,a,b) for(int i=(a);i<=(b);i++)
#define FORM(i,a,b) for(int i=(a);i>=(b);i--)
#define ls(a,b) (((a)+(b)) << 1)
#define rs(a,b) (((a)+(b)) >> 1)
#define getlc(a) ch[(a)][0]
#define getrc(a) ch[(a)][1] 

#define maxn 100000
#define maxm 100000
#define pi 3.1415926535898
#define _e 2.718281828459
#define INF 1070000000
using namespace std;
typedef long long ll;
typedef unsigned long long ull; 

template<class T> inline
void read(T& num) {
    bool start=false,neg=false;
    char c;
    num=0;
    while((c=getchar())!=EOF) {
        if(c==‘-‘) start=neg=true;
        else if(c>=‘0‘ && c<=‘9‘) {
            start=true;
            num=num*10+c-‘0‘;
        } else if(start) break;
    }
    if(neg) num=-num;
}
/*==================split line==================*/
struct Infor{
	int pos,x,id,op,k,l,r,cur;
	//k,l,r,cur,id专门针对询问,分别表示询问第k大,l,r表示询问的范围,cur表示贡献---->放到程序中更好解释,表示询问的序号
	//pos表示修改或原序列数值在序列中的位置
	//op表示操作类型:1表示在序列中的元素,2表示修改前的元素-----这里又有一个奇技淫巧,我们将所有的信息--->原序列,修改,询问放在一个区间中,那么对于一个修改,到目前为止应该在序列中的元素则为修改后的元素,就在区间中新建一个位置放原来那个数,标记为2,修改后的数标记为1.
	//x表示修改或原序列中的数值
}q[maxn],temp[maxn];
int c[maxn],a[maxn],ans[maxn],tmp[maxn];
bool mark[maxn];
int n,m,cnt=0,num=0;
//树状数组相关==================================================
void add(int x,int t){
	while (x<=n){
		c[x]+=t;
		x+=lowbit(x);
	}
}
int query(int x){
	int ret=0;
	while(x>0){
		ret+=c[x];x-=lowbit(x);
	}
	return ret;
}
//==============================================================
void solve(int l,int r,int L,int R){//l,r表示正在进行二分的区间,L,R表示答案的范围
	if (l>r) return;
	if (L==R) { //如果答案已经确定了,那么这个区间内每一个询问的答案都是L
		FORP(i,l,r) if (q[i].op==3) ans[q[i].id]=L;
		return;
	}
	int mid=rs(L,R);
	FORP(i,l,r){ //对于区间内每个的每个操作
		if (q[i].op==1 && q[i].x<=mid) add(q[i].pos,1);
		//如果这个元素应该在序列中存在,并且它会对当前的答案产生影响,我们将该位置加上1.
		if (q[i].op==2 && q[i].x<=mid) add(q[i].pos,-1);
		//奇技淫巧
		//如果这个元素的op为2,那么之前一定有一个相等元素其op为1并且其已经对答案造成影响,如今这个元素被修改那么我们相应的要将它对答案的影响减去.
		if (q[i].op==3) tmp[i]=query(q[i].r)-query(q[i].l-1);
		//如果当前操作是询问操作,注意到一个时间性质,在这个询问之后的修改对这个询问没有影响,我们统计这个点询问范围内的贡献.
	}
	FORP(i,l,r){ //重置树状数组
		if (q[i].op==1 && q[i].x<=mid) add(q[i].pos,-1);
		if (q[i].op==2 && q[i].x<=mid) add(q[i].pos,1);
	}
	int tot=0;//统计在将区间二分时靠右操作的个数
	FORP(i,l,r){//划分操作
		if (q[i].op==3){ //这里就能体现cur的作用,如果当前比判断答案小的个数加上已经比判断答案小的个数大于k,那么我们要将判断答案减小,对于所有要这么操作的我们将它划到左边----于是就可以统一将判定答案的范围减小
			if (q[i].cur+tmp[i]>=q[i].k) mark[i]=true,tot++;
			else mark[i]=false,	q[i].cur+=tmp[i];
		}
		else{
			if (q[i].x<=mid) tot++,mark[i]=true; //对答案造成影响的,判定答案减小后仍可能造成影响.所以我们将它划到右边
			else mark[i]=false;
		}
	}
	int la=l,lb=l+tot;
	FORP(i,l,r)
		if (mark[i]) temp[la++]=q[i];
			else temp[lb++]=q[i];
	FORP(i,l,r) q[i]=temp[i]; //复制粘贴的过程
	solve(l,la-1,L,mid);
	solve(la,lb-1,mid+1,R); //整体二分的过程. 非常6
}
int main(){
	//freopen("a.in","r",stdin);
	//freopen("tmp.out","w",stdout);
	read(n); read(m);
	FORP(i,1,n){
		read(a[i]);
		q[++num].x=a[i]; q[num].pos=i; q[num].op=1; q[num].id=0;
	} //读入原序列
	FORP(i,1,m){
		char s[5]; scanf("%s",s);
		if (s[0]==‘Q‘){
			int x,y,z; read(x); read(y); read(z);
			q[++num].l=x; q[num].r=y; q[num].k=z;
			q[num].id=++cnt; q[num].op=3;
		}//读入询问
		else {
			int x,t;
			read(t); read(x);
			q[++num].x=a[t]; q[num].pos=t; q[num].id=0; q[num].op=2;//奇技淫巧,增加一个操作
			q[++num].x=x; q[num].pos=t; q[num].id=0; q[num].op=1;
			a[t]=x;
		}//读入修改操作
	}
	solve(1,num,0,INF); //整体二分
	FORP(i,1,cnt) printf("%d\n",ans[i]);//输出
}
时间: 2024-08-02 23:14:07

BZOJ 1901 & 整体二分的相关文章

bzoj 2527 Meteors - 整体二分 - 树状数组

Description Byteotian Interstellar Union (BIU) has recently discovered a new planet in a nearby galaxy. The planet is unsuitable for colonisation due to strange meteor showers, which on the other hand make it an exceptionally interesting object of st

BZOJ 3110: [Zjoi2013]K大数查询 [整体二分]

有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. N,M<=50000,N,M<=50000a<=b<=N1操作中abs(c)<=N2操作中c<=Maxlongint 之前用树套树抄过一次...然而我并不适合写那玩意儿... 加上时间序的整体二分 普通的整体二分先处理了所有$[l,mid]$的影响因子在计算询问的答案来分组

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

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

BZOJ 2738 矩阵乘法(整体二分+二维树状数组)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=2738 [题目大意] 给出一个方格图,询问要求求出矩阵内第k小的元素 [题解] 我们对答案的大小进行整体二分,用二维树状数组维护二维区间和, 将超过数量的分治到左区间,不满足的分治到右区间即可. [代码] #include <cstdio> #include <algorithm> #include <cstring> using namespace std;

BZOJ 2527 [Poi2011]Meteors(整体二分)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=2527 [题目大意] 有N个成员国.现在它发现了一颗新的星球, 这颗星球的轨道被分为M份(第M份和第1份相邻),第i份上有第Ai个国家的太空站. 这个星球经常会下陨石雨.BIU已经预测了接下来K场陨石雨的情况. BIU的第i个成员国希望能够收集Pi单位的陨石样本. 你的任务是判断对于每个国家,它需要在第几次陨石雨之后,才能收集足够的陨石. [题解] 如果枚举每场陨石雨,树状数组查询每个

BZOJ 2527 Poi2011 Meteors 整体二分+线段树 / 可持久化线段树(MLE)

题目大意:给定一个环,每个节点有一个所属国家,k次事件,每次对[l,r]区间上的每个点点权加上一个值,求每个国家最早多少次操作之后所有点的点权和能达到一个值 首先我们考虑暴力想法 对于每个国家分开讨论 二分操作次数 但是这样每次Judge的时候我们要模拟1~mid所有的操作 浪费在这里的复杂度实在太大 这样做每个国家需要模拟O(klogk)次操作 时间复杂度O(nklogk) TLE 我们需要对浪费在这里的复杂度做一些改进 1.可持久化线段树(MLE) 每次二分一个mid之后 我们要找到mid次

BZOJ 2738 矩阵乘法 整体二分+二维树状数组

题目大意:给定一个矩阵,多次求某个子矩阵中的第k小 分块解法见 http://blog.csdn.net/popoqqq/article/details/41356899 <论除最小割外题目解法从来与题目名称无关系列> 整体二分 Solve(x,y,S)表示处理答案在[x,y]区间内的询问集合S 预先将所有数按照大小排序 每次将[1,mid]之间的数插入树状数组 然后对于分治内部的每一个询问 去树状数组中查询相应子矩阵的数值 如果小于等于k就划分到左集合S1 否则划分到右集合S2 然后Solv

bzoj 1146 网络管理Network (CDQ 整体二分 + 树刨)

题目传送门 题意:求树上路径可修改的第k大值是多少. 题解:CDQ整体二分+树刨. 每一个位置上的数都会有一段持续区间 根据CDQ拆的思维,可以将这个数拆成出现的时间点和消失的时间点. 然后通过整体二分第k大思路 + 树炮询问路径上出现点的个数就好了. 说一下整体二分的思路. 先假设第k大的值是mid, 然后按照时间顺序,出现一个数<=mid标记这个数的位置为1, 消失一个数<=mid,标记这个数的位置为0. 然后对于询问来说,询问路径上的值, 与 k进行比较, 如果 值 >= k则说明

[决策单调性][整体二分] Bzoj P2216 Lightning Conductor

Description 已知一个长度为n的序列a1,a2,...,an.对于每个1<=i<=n,找到最小的非负整数p满足 对于任意的j, aj < = ai + p - sqrt(abs(i-j)) Input 第一行n,(1<=n<=500000)下面每行一个整数,其中第i行是ai.(0<=ai<=1000000000) Output n行,第i行表示对于i,得到的p Sample Input 6532424 Sample Output 235354 题解 随手