zoukankan      html  css  js  c++  java
  • 整体二分

    思想

    对于某些难以直接求解的问题,二分答案显然是个不错的想法

    但是对于某些多次询问的问题,二分答案的复杂度可以会达到(O(q*n*logn))

    但是如果我们用某种玄学方法,把所有询问一起二分答案,复杂度就会玄学地降低

    不过在整体二分的过程中,我们需要用到分治的思想,这也是为什么很多博客把(cdq)分治和整体二分放在一起

    操作

    拿静态区间(k)小值来举个例子

    一般来说,我们有函数(solve(tl,tr,l,r)),表示值域在([tl,tr])之间包含操作([l,r])

    我们先把所有操作和询问放在一起,构造函数(solve(-inf,inf,1,n+m))表示在值域([-inf,inf])之间,包含操作([1,n+m]),操作包括插入数字和询问(k)大值

    我们考虑在值域上二分答案,二分出一个(mid=(tl+tr)>>1),然后我们把所有插入数字小于等于(mid)的操作先插入树状数组中,然后放入一个数组(q1)中,大于(mid)的操作直接放入数组(q2)

    再考虑(check)询问,查询树状数组内每个询问对应的([l_i,r_i]),如果数字个数(sum)大于等于(k_i),就将询问放入(q1),否则将(k_i)减去(sum)再放入(q2)

    然后我们将(q1)中的操作放回原数组中的前(cnt1)个位置,(q2)中的操作放入后面的位置

    每次处理完之后向下分治的时候,分治为(solve(tl,mid,l,l+cnt1-1))(solve(mid+1,tr,l+cnt1,r))

    我们分治的边界是(l>r)表示当前值域内无操作,或者(tl==tr)表示求出了答案

    配合代码食用就很好理解呢~

    #include<bits/stdc++.h>
    using namespace std;
    namespace red{
    #define lowbit(x) ((x)&(-x))
    	inline int read()
    	{
    		int x=0;char ch,f=1;
    		for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    		if(ch=='-') f=0,ch=getchar();
    		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    		return f?x:-x;
    	}
    	const int N=2e5+10,inf=1e9;
    	int n,m,tot;
    	struct point
    	{
    		int l,r,k,opt,id;
    	}q[N<<1],q1[N<<1],q2[N<<1];
    	int a[N],ret[N];
    	int tr[N];
    	inline void add(int x,int k)
    	{
    		for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=k;
    	}
    	inline int query(int y)
    	{
    		int ret=0;
    		for(int i=y;i;i-=lowbit(i)) ret+=tr[i];
    		return ret;
    	}
    	inline void solve(int tl,int tr,int l,int r)
    	{
    		if(l>r) return;//当前区间内无操作
    		if(tl==tr)//求出了答案
    		{
    			for(int i=l;i<=r;++i)
    				if(q[i].opt==2) ret[q[i].id]=tl;//统计答案
    			return;
    		}
    		int mid=(tl+tr)>>1,cnt1=0,cnt2=0,tmp;
    		for(int i=l;i<=r;++i)
    		{
    			if(q[i].opt==1)//插入
    			{
    				if(q[i].l<=mid) add(q[i].id,q[i].r),q1[++cnt1]=q[i];//小于mid插入树状数组
    				else q2[++cnt2]=q[i];
    			}
    			else
    			{
    				tmp=query(q[i].r)-query(q[i].l-1);//查询当前树状数组内有多少数字
    				if(q[i].k<=tmp) q1[++cnt1]=q[i];//大于等于k
    				else q[i].k-=tmp,q2[++cnt2]=q[i];//小于k
    			}
    		}
    		for(int i=1;i<=cnt1;++i)
    			if(q1[i].opt==1) add(q1[i].id,-q1[i].r);//消除影响
    		for(int i=1;i<=cnt1;++i) q[l+i-1]=q1[i];//放回原操作数组
       		for(int i=1;i<=cnt2;++i) q[l+cnt1+i-1]=q2[i];
       		solve(tl,mid,l,l+cnt1-1);//分治
        	solve(mid+1,tr,l+cnt1,r);
    	}
    	inline void main()
    	{
    		n=read(),m=read();
    		for(int i=1;i<=n;++i)
    		{
    			a[i]=read();
    			q[++tot]=(point){a[i],1,0,1,i};//保证了插入数字在前面,不用分开处理了
    		}
    		for(int l,r,k,i=1;i<=m;++i)
    		{
    			l=read(),r=read(),k=read();
    			q[++tot]=(point){l,r,k,2,i};
    		}
    		solve(-inf,inf,1,tot);
    		for(int i=1;i<=m;++i) printf("%d
    ",ret[i]);
    	}
    }
    signed main()
    {
    	red::main();
    	return 0;
    }
    

    复杂度

    考虑对值域进行分治,那么复杂度显然是一个(logV)

    注意到尽管每次我们对操作的分配并不均匀,但是由于每个深度的操作数之和都是(O(nlogn)),所以我们的复杂度是(O(nlognlogV))

    当然如果我们提前离散化的话,复杂度可以降低了(O(nlognlogn))但我懒没这么做

    例题

    本菜鸡目前见到的整体二分都不穿衣服,所以只能放一件稍微穿了点衣服的

    洛谷P3527 [POI2011]MET-Meteors

    有推荐题目欢迎留言

  • 相关阅读:
    BZOJ2563 阿狸和桃子的游戏
    BZOJ2460 Beijing2011元素(线性基+贪心)
    BZOJ2458 Beijing2011最小三角形(分治)
    BZOJ2442 Usaco2011 Open修剪草坪(动态规划+单调队列)
    Luogu2257 YY的GCD/BZOJ2818 Gcd加强版(莫比乌斯反演+线性筛)
    BZOJ2428 HAOI2006均分数据(模拟退火)
    BZOJ2440 中山市选2011完全平方数(容斥原理+莫比乌斯函数)
    洛谷 P1783 海滩防御 解题报告
    洛谷 P2431 正妹吃月饼 解题报告
    洛谷 P2751 [USACO4.2]工序安排Job Processing 解题报告
  • 原文地址:https://www.cnblogs.com/knife-rose/p/12049069.html
Copyright © 2011-2022 走看看