[POJ1741] Tree [点分治]

题意:给出一颗有 N N 个点的树。给定一个 K K ,求 C a r d (    { ( u , v ) d i s t ( u , v ) K }    ) Card(\;\{(u,v)|dist(u,v)\le K\}\;)。
( N 1 0 4 , d i s t ( u , v ) 1 0 3 ) (N\le10^4,dist(u,v)\le10^3)

点分治裸题

考虑以一颗 r o o t root 为根的树,要统计其中长度 K \le K 路径数量的和 S u m Sum

如果直接打算全部算出来显然不怎么现实,我们先只考虑经过 r o o t root 的那些路径。

d i s ( x ) = d i s t ( r o o t , x ) dis(x)=dist(root,x) 。有 d i s t ( u , v ) = d i s ( u ) + d i s ( v ) 2 d i s ( l c a u , v ) dist(u,v)=dis(u)+dis(v)-2*dis(lca_{u,v})
设集合 X = { ( u , v ) f a t h e r ( l c a ( u , v ) ) = r o o t , d i s t ( l c a ( u , v ) , u ) + d i s t ( l c a ( u , v ) , v ) + 2 d i s ( l c a ( u , v ) ) K } X=\{(u,v)|father(lca(u,v))=root,dist(lca(u,v),u)+dist(lca(u,v),v)+2*dis(lca(u,v))\le K\}
S u m = C a r d ( { ( u , v ) d i s ( u ) + d i s ( v ) K } ) C a r d ( X ) Sum=Card(\{(u,v)|dis(u)+dis(v)\le K\})-Card(X)

如何求 C a r d ( { ( u , v ) d i s ( u ) + d i s ( v ) K } ) Card(\{(u,v)|dis(u)+dis(v)\le K\})
算出所有的 d i s ( x ) dis(x) ,排序。
对于每个 i i m a x ( j ) max(j) 满足 d i s ( i ) + d i s ( j ) K dis(i)+dis(j)\le K 那么 i i 的贡献就是 j i j-i
显然,如果 i i 增大, j j 一定不会增大,所以可以 Θ ( N ) \Theta(N) 统计贡献
也可以尺取法。

现在考虑不经过 r o o t root 的那些路径。

去掉了 r o o t root 之后,每个以它的儿子为根的子树可以分别单独提出来考虑(分治)
如果直接沿着儿子走下去会爆炸(链的情况很显然),所以就需要考虑有没有更好的方法
复杂度显然跟每一步的子树大小有关,于是把这个作为标准考虑每步让子树大小至少 / 2 /2
也就是说要尽量平均地分配子树,更直观地讲就是让最大子树最小
所以理想的分治方案就是找重心提根来 d i v i d e divide

总的复杂度为 Θ ( N l o g 2 N ) \Theta(Nlog_2N)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<cctype>
using namespace std;
int N,K,tot=0,root,mn;
int head[10005]={},nxt[20005]={},to[20005]={},val[20005]={};
bool vis[20005]={};
int siz[10005]={},mxsiz[10005]={},curtotal=0,ans=0;
int dis[10005]={};
#define add_edge(a,b,c) nxt[++tot]=head[a],head[a]=tot,to[tot]=b,val[tot]=c;
void getdis(int x,int fa,int d)
{
	dis[++dis[0]]=d;
	for(int v,i=head[x];i;i=nxt[i])
	{
		v=to[i]; if(vis[v]||v==fa)continue;
		getdis(v,x,d+val[i]);
	}
}
int solve(int x,int add)
{
	dis[0]=0;
	getdis(x,0,add);
	sort(dis+1,dis+1+dis[0]);
	int ret=0,iter=dis[0];
	for(int i=1;i<=dis[0];++i)
	{
		while(dis[iter]+dis[i]>K)--iter;
		if(iter<=i)break;
		ret+=iter-i;
	}
	return ret;
}
void getroot(int x,int fa)
{
	siz[x]=1; mxsiz[x]=0;
	for(int v,i=head[x];i;i=nxt[i])
	{
		v=to[i]; if(vis[v]||v==fa)continue;
		getroot(v,x); siz[x]+=siz[v];
		mxsiz[x]=max(mxsiz[x],siz[v]);
	}
	mxsiz[x]=max(mxsiz[x],curtotal-siz[x]);
	if(mxsiz[x]<mn)mn=mxsiz[x],root=x;
}
void divide(int x)
{
	ans+=solve(x,0);
	vis[x]=1;
	for(int v,i=head[x];i;i=nxt[i])
	{
		v=to[i]; if(vis[v])continue;
		ans-=solve(v,val[i]);
		curtotal=siz[v]; mn=2147483647; getroot(v,0);
		divide(root);
	}
}
int main()
{
	while(~scanf("%d%d",&N,&K))
	{
		if((!N)&&(!K))return 0;
		
		memset(vis,0,sizeof(vis));
		ans=0,tot=0,mn=2147483647,curtotal=N;
		for(int i=1;i<=N;++i)head[i]=0;
		
		for(int u,v,w,i=1;i<N;++i)
		{
			scanf("%d%d%d",&u,&v,&w);
			add_edge(u,v,w);
			add_edge(v,u,w);
		}
		
		getroot(N>>1,0); 
		divide(root);
		
		printf("%d\n",ans);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Estia_/article/details/82881667