史上最强分治讲解

1.逆序对

#include<bits/stdc++.h>
#define N 100005
using namespace std;
int n,ans,a[N],cur[N];
int merge(int l,int r){
	int mid=(l+r)>>1,i=l,j=mid+1,cnt=0;
	for(int k=l;k<=r;k++){
		if(j>r||i<=mid&&a[i]<a[j]) cur[k]=a[i++];
		else cur[k]=a[j++],cnt+=mid-i+1;
	}
	for(int k=l;k<=r;k++) a[k]=cur[k];
	return cnt;
}
int solve(int l,int r){
	if(l<r){
	int mid=(l+r)>>1;
	solve(l,mid),solve(mid+1,r);
	ans+=merge(l,r);
    }
    return ans;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	cout<<solve(1,n);
	return 0;
}

举个例子

8 5 2 4 6 3 2 9

8 5 2 4   与   6 3 2 9

再分

8 5 ,2 4,  6 3, 2 9

5 8,ans++  ;2 4;6,3,ans++;2 9

2 4 5 8 ans+=4 ; 2 3 6 9 ans+=2

2 2 3 4 5 6 8 9 ans+=7

ans=14

2.平面上的分治

#include<bits/stdc++.h>
#define N 200005
using namespace std;
int n,inf=0x3fffffff;
struct Node{double x,y;}pos[N],cur[N];
bool cmp1(Node a,Node b){
     if(a.x==b.x) return a.y<b.y;
	 return a.x<b.x;
}
bool cmp2(Node a,Node b){
     if(a.y==b.y)return a.x<a.y;
	 return a.y<b.y;
}
double dis(Node a,Node b){
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
double solve(int l,int r){
	if(l==r) return inf;
	if(l+1==r) return dis(pos[l],pos[r]);
	int mid=(l+r)>>1;
	double d1=solve(l,mid),d2=solve(mid+1,r);
	double d=min(d1,d2);
	int tmp=0;
	for(int i=l;i<=r;i++){
		if(abs(pos[mid].x-pos[i].x)<d) 
		cur[++tmp]=pos[i];
	}
	sort(cur+1,cur+tmp+1,cmp2);
	for(int i=1;i<=tmp;i++){
		for(int j=i+1;j<=tmp&&cur[j].y-cur[i].y<d;j++){
	      	double d3=dis(cur[i],cur[j]);
	      	d=min(d,d3);
	    }
	}
	return d;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%lf%lf",&pos[i].x,&pos[i].y); 
	sort(pos+1,pos+n+1,cmp1);
	printf("%.4f",solve(1,n));
	return 0;
}

x坐标排序

假设画一条线,两边各有n/2个点

现在的最短为min(左边最短,右边最短,左右各选一个最短)

前两个可以继续分治

现在考虑最后一个

假设d=min(左边最短,右边最短) 

我们枚举mid-d--mid+d中的点

然后看哪两个<d 然后就可以更新了

(可以先排个序)

3.点分治

给定一棵有n个点的树
询问树上距离=k的点对有多少。

#include<bits/stdc++.h>
#define N 100005
using namespace std;
int first[N],next[N*2],to[N*2],w[N*2],tot;
int n,root,tmp[N],cur[N],ans,vis[N],k;
int S,Size[N],Maxson[N],Max=0x3fffffff,sum;
int read(){
	int cnt=0;char ch=0;
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) cnt=cnt*10+(ch-'0'),ch=getchar();
	return cnt;
}
void add(int x,int y,int z){
	next[++tot]=first[x];
	first[x]=tot;
	to[tot]=y,w[tot]=z;
}
void getroot(int cur,int fa){
	Size[cur]=1;
	for(int i=first[cur];i;i=next[i]){
		int t=to[i];
		if(t==fa||vis[t]) continue;
		getroot(t,cur);
		Size[cur]+=Size[t];
		Maxson[cur]=max(Maxson[cur],Size[t]);
	}
	Maxson[cur]=max(Maxson[cur],S-Size[cur]);
	//不为根节点时,除去它的子树,上面还有一坨
	if(Maxson[cur]<Max&&Maxson[cur]!=-1) root=cur,Max=Maxson[cur];
}
void getdis(int cur,int fa,int dis){
	 tmp[++sum]=dis;
	 for(int i=first[cur];i;i=next[i]){
	 	int t=to[i];
	 	if(t==fa||vis[t]) continue;
	 	getdis(t,cur,dis+w[i]);
	 }
}
int solve(int x,int d){
	sum=0;
	getdis(x,0,d);
	sort(tmp+1,tmp+sum+1);
	int l=1,r=sum,res=0;
	while(l<=r){
		if(tmp[l]+tmp[r]<=k) res+=r-l,l++;
		else r--;
	}
	return res;
}
void getans(int x){
	ans+=solve(x,0);
	vis[x]=1;
	for(int i=first[x];i;i=next[i]){
		int t=to[i];
		if(vis[t]) continue;
		ans-=solve(t,w[i]);
		S=Size[t],root=0;
		Max=0x3fffffff,getroot(t,0);//Maxson不会变大 
		getans(root);
	}
}
int main()
{
	//freopen("1.in","r",stdin);
	n=read(),k=read();
	for(int i=1;i<n;i++){
		int x=read(),y=read(),z=read();
		add(x,y,z),add(y,x,z);
	}
	memset(Maxson,-1,sizeof(Maxson));
	getroot(1,0);
	getans(root);//分治 
	cout<<ans;
	return 0;
}

三步走

1.找重心(getroot)

2.处理经过重心的

3.没经过的丢给儿子,重复1

注意这句

ans-=solve(t,w[i]);

找到两条经过root的路径后,有可能两条在同一棵子树,所以要去掉

猜你喜欢

转载自blog.csdn.net/sslz_fsy/article/details/81273296