Loading

【笔记】分治算法

来自\(\texttt{SharpnessV}\)省选复习计划中的分治算法


分治,顾名思义,分而治之,一般能将 \(N^2\) 的时间复杂度优化至 \(N \log N\)\(N\log^2 N\)


P7415 [USACO21FEB] Count the Cows G

观察一个对于边长为\(3^n\)的正方形,拆分成 \(9\) 块。

1 0 1
0 1 0
1 0 1

其中 \(1\) 表示边长为 \(3^{n-1}\) 的正方形,\(0\) 表示全零正方形。

我们发现这构成递归结构,我们讨论一下对角线过这个正方形的哪些块即可。

时间复杂度\(\rm O(Q\log^2_{3}\ d_i )\)

Code


整体二分

P3527 [POI2011]MET-Meteors

如果一次询问,我们可以二分在\(\rm O(N\log N)\)的时间内解决,但是有多次询问,就可以考虑整体二分。

整体二分就是把所有的询问放到一起二分。

主要思路是设计递归solve(l,r,L,R),表示在\([l,r]\)之间的询问,答案在\([L,R]\)中。

每次查找\([L,R]\)的中点\(mid\)\(\rm check\)一下\([l,r]\)之间的答案,如果满足条件放到左边,否则放到右边,递归下去处理。

显然每递归一次答案的区间减半,所以最多递归 \(\log T\) 层,每一层有 \(N\) 个询问需要\(\rm check\),如果\(\rm check\)的时间复杂度是\(f(N)\),则整个算法的时间复杂度为\(\rm O(Nf(N)\log T)\)。(\(\rm T\)为值域,下同)

这道题,我们可以二分天数,然后将所有询问塞到一起\(\rm check\),就可以做到\(\rm O(N\log ^2N)\)的时间复杂度。

Code


P1527 [国家集训队]矩阵乘法

二维矩阵第\(K\)小。

我们都知道区间第\(K\)小可持久化线段树是最优的,但是放到矩阵上时间和空间复杂度就不可接受了。

区间第\(K\)小还可以整体二分,考虑扩展到矩阵上。

考虑二分当前答案,将\(\le mid\)的数在对应的位置\(+1\),对于每个查询只需要判断矩阵和是否\(\ge k\)即可,这可以用二维树状数组维护。

时间复杂度\(\rm O((N^2+Q)\log T\log^2 N)\)

Code


P2617 Dynamic Rankings

区间带修第\(K\)小。

考虑扩展可持久化线段树,需要支持带修,只用树状数组套可持久化线段树即可,时间和空间复杂度都是\(\rm O(N\log^2N)\),空间比较卡。

考虑整体二分,我们发现修改操作,在二分时对应的是单点加减,仍然可以树状数组维护,稍加修改模板即可。

时间复杂度仍然是\(\rm O(N\log^2 N)\),空间复杂度优化为线性。

#include<bits/stdc++.h>
using namespace std;
int n,m,u[100005];
struct node{
	int x,y,z,op;
}q[500005];
int T=0,tot=0,b[200005],t,o[200005],pt,c[200005],ans[200005];
void add(int x,int val){for(;x<=pt;x+=x&-x)c[x]+=val;}
int ask(int x){
	int sum=0;
	for(;x;x-=x&-x)sum+=c[x];
	return sum;
}
node ls[500005],rs[500005];
void solve(int l,int r,int st,int ed){
	if(st>ed)return;
	if(l==r){
		for(int i=st;i<=ed;i++)if(q[i].op)ans[q[i].op]=l;
		return;
	}
	int mid=(l+r)>>1,lt=0,rt=0;
	for(int i=st;i<=ed;i++){
		if(!q[i].op){
			if(q[i].y<=mid){
				add(q[i].x,q[i].z);
				ls[++lt]=q[i];
			}
			else rs[++rt]=q[i];
		}
		else{
			int sum=ask(q[i].y)-ask(q[i].x-1);
			if(sum>=q[i].z)ls[++lt]=q[i];
			else{
				rs[++rt]=q[i];
				rs[rt].z-=sum;
			}
		}
	}
	for(int i=st;i<=ed;i++)if(!q[i].op&&q[i].y<=mid)add(q[i].x,-q[i].z);
	for(int i=1;i<=lt;i++)q[st+i-1]=ls[i];
	for(int i=1;i<=rt;i++)q[st+lt+i-1]=rs[i];
	solve(l,mid,st,st+lt-1);solve(mid+1,r,st+lt,ed);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&u[i]),o[++t]=u[i];
	for(int i=1;i<=n;i++)q[++T].op=0,q[T].x=i,q[T].y=u[i],q[T].z=1;
	for(int i=1;i<=m;i++){
		char opt[2];int x,y,z;
		scanf("%s%d%d",opt,&x,&y);
		if(opt[0]=='C'){
			o[++t]=y;
			q[++T].op=0;q[T].x=x;q[T].y=u[x];q[T].z=-1;
			q[++T].op=0;q[T].x=x;q[T].y=y;q[T].z=1;u[x]=y;
		}
		else{
			scanf("%d",&z);
			q[++T].op=++tot;q[T].x=x;q[T].y=y;q[T].z=z;
		}
	}
	sort(o+1,o+t+1);
	for(int i=1;i<=t;i++)
	  if(i==1||o[i]^o[i-1])b[++pt]=o[i];
	for(int i=1;i<=T;i++)
	  if(!q[i].op)q[i].y=lower_bound(b+1,b+pt+1,q[i].y)-b;
	solve(1,pt,1,T);
	for(int i=1;i<=tot;i++)printf("%d\n",b[ans[i]]);
	return 0;
}

P4175 [CTSC2008]网络管理

直接树剖整体二分可以做到\(\rm O(N\log^3 N)\)

考虑优化,修改操作是单点修改,则需要查询链上和,而查询链上和并不好求。

先差分一下,链上查询可以拆成四个点到根的路径。

考虑转换一下,对树上一点修改,只会影响到它的子树中的点到根的路径。而子树修改可以用\(\rm DFS\)序转化为一个区间,这样就可以做到\(\rm O(N\log^2N)\)的时间复杂度。

转换后也可以用树状数组套可持久化线段树,本质上没有区别。

Code


P3250 [HNOI2016]网络

单次询问二分可做,考虑整体二分。

二分未被影响的请求中重要度的最大值,重要度小于当前值的路径\(+1\),然后单点查询判断是否所有询问都被影响。

三只\(\log\)碾过去。。。

发现只有链上加和单点查询,延续上一题的讨论,链上加改为单点加,单点查询转化为子树查询。

Code


P3242 [HNOI2015] 接水果

给定若干路径,每次查询一条路径的所有子路径中第\(k\)小的权值。

二分答案,我们只有判断一个路径有多少子路径。

反过来,需要求一个路径可以成为哪些路径的子路径。

不难发现能够包含当前路径的路径,一定是两端在当前路径两段的子树内,对应一个平面上的矩形,直接扫描线即可。

Code


P3332 [ZJOI2013]K大数查询

第一眼发现求集合并集非常不可做。

然后发现是可重集,所以是区间修改第\(k\)小模板,整体二分碾过去。

Code


\(\rm CDQ\)分治

P3810 【模板】三维偏序(陌上花开)

二维偏序就是我们所熟知的逆序对。可以用归并排序解决。

观察一下归并排序,发现合并过程中,左边一半的第一维一定小于右边的第一维,所以我们只用考虑第二维的限制,问题极大的简化了。

\(\rm CDQ\)分治就是基于这样的思想,我们先将所有点按第一维从小到大排序,则合并左右两段时,左边第一维一定小于右边第一维。

我们考虑第二维限制,第二维类似于归并排序,在合并的过程中满足第二维限制。

第三维,用树状数组维护,左边的点在树状数组中插入,右边的点查询前缀和。

时间复杂度\(\rm O(N\log^2 N)\)

Code


P3157 [CQOI2011]动态逆序对

没有修改就是二维偏序。

存在修改,我们可以再增加一个维度,表示时间轴,这就变成了三维偏序模板,套用\(\rm CDQ\)分治即可。

Code


P4169 [Violet]天使玩偶/SJY摆棋子

对于每个点,分别考虑左上,左下,右上,右下的最近点。

支持修改后,我们加入时间轴,对于每个点,需要计算时间在它前面,且在它左下角的最近点,这就是三维偏序模板。

四个方向,我们只用将所有点每次旋转\(90\)°即可。

Code


P4390 [BOI2007]Mokia 摩基亚

支持插入二维数点,三维偏序模板。

Code


P2487 [SDOI2011]拦截导弹

我们设\(f[i]\)表示以第\(i\)个导弹结束最多能拦截的导弹个数。

如果\(f[j]\to f[i]\),则需要满足\(j<i,h_j\ge h_i,v_j\ge v_i\)

我们发现这和三维偏序很像,但是这是\(\rm DP\)

所以我们用\(\rm CDQ\)分治优化\(\rm DP\),每次先递归计算\([l,mid]\),然后用\([l,mid]\)更新\([mid+1,r]\),最后递归计算\([mid+1,r]\)

注意本题还要求一个点出现的方案数,需要正反两遍分治。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 50005
using namespace std;
int n,o[N],b[N],T,f[N],u[N];double g[N];
struct node{
	int tm,h,v;
}a[N];
bool cmp(node x,node y){return x.tm<y.tm;}
bool cmp1(node x,node y){return x.h>y.h;}
bool cmp2(node x,node y){return x.h<y.h;}
int c[N];double d[N];
inline void add(int x,int p,double q){
	for(;x<=T;x+=x&-x){
		if(c[x]==p)d[x]+=q;
		else if(c[x]<p)c[x]=p,d[x]=q;
	}
}
inline void clear(int x){for(;x<=T;x+=x&-x)c[x]=0xcfcfcfcf,d[x]=0;}
typedef pair<int,double> Pr;
inline Pr ask(int x){
	Pr now;now.first=0xcfcfcfcf;now.second=0;
	for(;x;x-=x&-x)if(c[x]>now.first)now=make_pair(c[x],d[x]);else if(c[x]==now.first)now.second+=d[x];
	return now;
}
void solve(int l,int r){
	if(l==r){
		if(!f[l])f[l]=1,g[l]=1;
		else f[l]++;
		return;
	}
	int mid=(l+r)>>1;
	solve(l,mid);
	sort(a+mid+1,a+r+1,cmp1);
	int j=l;
	rep(i,mid+1,r){
		while(j<=mid&&a[j].h>=a[i].h)add(T-a[j].v+1,f[a[j].tm],g[a[j].tm]),j++;
		Pr cur=ask(T-a[i].v+1);
		if(cur.first>f[a[i].tm])f[a[i].tm]=cur.first,g[a[i].tm]=cur.second;
			else if(cur.first==f[a[i].tm])g[a[i].tm]+=cur.second;
	}
	while(j>l)j--,clear(T-a[j].v+1);
	sort(a+mid+1,a+r+1,cmp);
	solve(mid+1,r);
	sort(a+l,a+r+1,cmp1);
}
int f_[N];double g_[N];
void calc(int l,int r){
	if(l==r){
		if(!f_[l])f_[l]=1,g_[l]=1;
		else f_[l]++;
		return;
	}
	int mid=(l+r)>>1;
	calc(mid+1,r);
	sort(a+l,a+mid+1,cmp2);
	int j=mid+1;
	rep(i,l,mid){
		while(j<=r&&a[j].h<=a[i].h)add(a[j].v,f_[a[j].tm],g_[a[j].tm]),j++;
		Pr cur=ask(a[i].v);
		if(cur.first>f_[a[i].tm])f_[a[i].tm]=cur.first,g_[a[i].tm]=cur.second;
			else if(cur.first==f_[a[i].tm])g_[a[i].tm]+=cur.second;
	}
	while(j>mid+1)j--,clear(a[j].v);
	sort(a+l,a+mid+1,cmp);
	calc(l,mid);
	sort(a+l,a+r+1,cmp2);
}
int main(){
	memset(c,0xcf,sizeof(c));
	scanf("%d",&n);
	rep(i,1,n)a[i].tm=i,scanf("%d%d",&a[i].h,&a[i].v),o[i]=a[i].v;
	sort(o+1,o+n+1);
	rep(i,1,n)if(o[i]!=o[i-1])b[++T]=o[i];
	rep(i,1,n)a[i].v=lower_bound(b+1,b+T+1,a[i].v)-b;
	solve(1,n);sort(a+1,a+n+1,cmp);calc(1,n);
	int mx=0;double sum=0;
	rep(i,1,n)mx=max(mx,f[i]);
	rep(i,1,n)if(f[i]==mx)sum+=g[i];
	printf("%d\n",mx);
	rep(i,1,n)if(f[i]+f_[i]-1==mx)printf("%lf ",1.00*g[i]*g_[i]/sum);else printf("0 ");
	return 0;
}
posted @ 2021-12-16 17:21  7KByte  阅读(218)  评论(0编辑  收藏  举报