点分治

一、算法

  1. 定义:
    a) 重心:最大子树最小的节点。
  2. 步骤:
    a) 选择重心,统计经过重心的路径。
    b) 切割子树,然后向下对子树重复上述操作。
  3. 模板:如下例题1
    在这里插入图片描述
    在这里插入图片描述
    5号节点为整棵树的重心。
    在这里插入图片描述
    2、6、9号节点分别为子树的重心。
    在这里插入图片描述
    继续分治,直到子树大小为1。

二、时间复杂度与证明

时间复杂度:O(nlogn)
  重心的最大子树大小不超过 n 2 \frac{n}{2} 2n。(反正法:如果重心rt的子节点x的大小大于 n 2 \frac{n}{2} 2n,那么rt的最大子树显然比x的最大子树大,那么rt就不是重心了。)
  这样进行分治后,一个树被划分成若干子树,每一个新子树的大小不超过原树大小的一半,直到树的大小为1。每一个节点最多经历 l o g 2 n log_{2}n log2n次分治,所以总时间复杂度为 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n)

三、相关题目

1. CF 161 D

点分治裸题。
题意:各一颗树,求树上距离为k的点对数。
题解:点分治。统计答案时,用距离<=k点对的减去距离<k的点对。统计距离<=k的点对方法如下。

sort(re+1,re+cnt+1);
    l=1;
    r=cnt;
    while(l<r)
    {
        if(re[l]+re[r]<=k)
        {
            res+=(long long)(r-l);
            l++;
        }
        else
            r--;
    }

完整代码如下

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=5e5+10;
struct EDGE{
    int to,nxt;
}edge[N*2];
int t[N],siz[N],msiz[N],re[N],vist[N],dep[N];
int tot,n,k,cnt,rt,sum;
long long ans;
void addedge(int x,int y)
{
    edge[++tot].to=y;
    edge[tot].nxt=t[x];
    t[x]=tot;
}
void getrt(int x,int fa)
{
    int p,y;
    siz[x]=1;
    msiz[x]=0;
    p=t[x];
    while(p)
    {
        y=edge[p].to;
        if(y!=fa&&vist[y]==0)
        {
            getrt(y,x);
            siz[x]+=siz[y];
            msiz[x]=max(msiz[x],siz[y]);
        }
        p=edge[p].nxt;
    }
    msiz[x]=max(msiz[x],sum-siz[x]);
    if(msiz[x]<msiz[rt])
        rt=x;
}
void getdep(int x,int fa)
{
    int p,y;
    re[++cnt]=dep[x];
    p=t[x];
    while(p)
    {
        y=edge[p].to;
        if(vist[y]==0&&fa!=y)
        {
            dep[y]=dep[x]+1;
            getdep(y,x);
        }
        p=edge[p].nxt;
    }
}
long long cal(int x,int w)
{
    cnt=0;
    dep[x]=w;
    getdep(x,0);
    int l,r;
    long long res=0;
    sort(re+1,re+cnt+1);
    l=1;
    r=cnt;
    while(l<r)
    {
        if(re[l]+re[r]<=k)
        {
            res+=(long long)(r-l);
            l++;
        }
        else
            r--;
    }
    l=1;
    r=cnt;
    while(l<r)
    {
        if(re[l]+re[r]<k)
        {
            res-=(long long)(r-l);
            l++;
        }
        else
            r--;
    }
    return res;
}
void solve(int x)
{
    int p,y;
    vist[x]=1;
    ans+=cal(x,0);
    p=t[x];
    while(p)
    {
        y=edge[p].to;
        if(!vist[y])
        {
            ans-=cal(y,1);
            rt=0;
            sum=siz[y];
            getrt(y,x);
            solve(rt);
        }
        p=edge[p].nxt;
    }
}
int main()
{
    int i,x,y;
    scanf("%d%d",&n,&k);
    for(i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        addedge(x,y);
        addedge(y,x);
    }
    sum=n;
    msiz[0]=n;
    getrt(1,0);
    solve(rt);
    printf("%lld",ans);
    return 0;
}
2.IOI2011 Race

题意:给一棵树,找到权值和为k的最短路径。
思路:点分治。统计每一种权值的长度。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int inf = 1e6 + 10;
struct EDGE{
	int to, nxt, v;
	EDGE(){}
	EDGE(int x, int y, int z){to = x; nxt = y; v = z;}
}edge[N * 2];
int t[N], size[N], msize[N], dist[N], dep[N], mdist[M], vist[N];
int ans, cnt, tot, top, rt, k;
void addedge(int x,int y,int v)
{
	edge[++cnt] = EDGE(y, t[x], v);
	t[x] = cnt;
}
void getrt(int x,int f)
{
	size[x] = 1;
	msize[x] = 0;
	for(int p = t[x]; p; p = edge[p].nxt)
	{
		int y = edge[p].to;
		if(!vist[y] && f != y)
		{
			getrt(y, x);
			size[x] += size[y];
			msize[x] = max(msize[x], size[y]);
		}
	}
	msize[x] = max(msize[x], tot - size[x]);
	if(msize[rt] > msize[x])	rt = x;
}
void getdep(int x, int f, int v, int u)
{
	if(v > k)	return;
	dist[++top] = v;
	dep[top] = u;
	for(int p = t[x]; p; p = edge[p].nxt)
	{
		int y = edge[p].to;
		if(!vist[y] && y != f)
			getdep(y, x, v + edge[p].v, u + 1);
	}
}
void getans(int x, int f)
{
	top = 0;
	for(int p = t[x]; p; p = edge[p].nxt)
	{
		int y = edge[p].to;
		if(!vist[y] && y != f)
		{
			int pre = top;
			getdep(y, x, edge[p].v, 1);
			for(int i = pre + 1; i <= top; i++)
				if(k >= dist[i])	ans = min(ans, mdist[k - dist[i]] + dep[i]);
			for(int i = pre + 1; i <= top; i++)
				if(dist[i] <= k)	mdist[dist[i]] = min(mdist[dist[i]], dep[i]); 
		}
	} 
	for(int i = 1; i <= top; i++)
		if(dist[i] <= k)
			mdist[dist[i]] = inf;
}
void solve(int x)
{
	getans(x, 0);
	vist[rt] = 1;
	for(int p = t[x]; p; p = edge[p].nxt)
	{
		int y = edge[p].to;
		if(!vist[y])
		{
			tot = size[y];
			rt = 0; 
			getrt(y,x);
			solve(rt);
		}
	}
}
int main()
{
	int n, x, y, v;
	ans = inf;
	scanf("%d%d", &n, &k);
	for(int i = 1; i < n; i++)
	{
		scanf("%d%d%d", &x, &y, &v);
		x++; y++;
		addedge(x, y, v);
		addedge(y, x, v); 
	}
	msize[0] = inf;
	tot = n;
	getrt(1,0);
	for(int i = 1; i <= k; i++)
		mdist[i] = inf;
 	solve(rt);
 	if(ans == inf)
 		ans = -1;
	printf("%d",ans); 
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值