一、算法
- 定义:
a) 重心:最大子树最小的节点。 - 步骤:
a) 选择重心,统计经过重心的路径。
b) 切割子树,然后向下对子树重复上述操作。 - 模板:如下例题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;
}