POJ1741 点分治模板题

本文通过观看B站视频学习点分治,并实践了一道POJ1741题目。点分治策略类似CDQ和启发式分治,递归层次为logn,每层复杂度线性或更优。在解决树上两点距离小于k的问题中,通过分治求解并处理重复计算,最终实现有效计算任意两点间的距离。
摘要由CSDN通过智能技术生成

在B站看了点分治后,照着敲了模板题

https://www.bilibili.com/video/av53560289?from=search&seid=8511450481402711616

点分治感觉根之前学的CDQ,启发式分治,等很像,都是先分再治。

分的复杂度尽量logn,治的复杂度线性或带上logn。

即:递归logn层,每层的复杂度在可控范围内。

树上点分治:顾名思义,即按照点进行分治,每次删除一个点,对每个子树再进行一次处理。

如果删除的是重心,则最多删logn次,可以保证无大小大于1的子树。

这一题是求树上任意2点距离小于k的个数。

考虑根rt的树,2点距离要么是同一子树,要么是不同子树。

我们分治后,对每层的每个子树:先求出子树所有点到子树根的距离。

由于区分是否是同一子树比较困难,干脆我们就不区分,加多的后面再减去。

得到num个距离后,用排序+尺取Onlogn的求出任意2点距离小于k的个数。

这样会多算:同一子树2点间距离小于k,(因为每层递归都会算一遍,我们算不同子树刚好不重不漏)

所以后面求子树距离时减去任意2点距离和+子树根到父亲的距离小于k的个数(这个个数就是刚才父亲计算距离时多算的,可以再纸上画下就明白了,任意点到子树根的距离+子树到父亲的距离其实就是:这棵子树任意点到父亲的距离)

分治下去就算完了。

代码:

//点分治,分==找重心,治==尺取+排序 复杂度n*(logn)^2 
//#include <bits/stdc++.h>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int M = 1e4+7;
struct node
{
	int to,v,nxt;
}E[M*2];
int head[M*2];
int cnt;
void add(int x,int y,int v)//x-y有一条权值v的路径
{
	E[++cnt].to=y;
	E[cnt].nxt=head[x];
	E[cnt].v=v;
	head[x]=cnt;
}
int rs,rt;//rt即重心 ,rs为重儿子大小 
int vis[M];//是否删除该点 
int size[M];
int dis[M];//当前子树每个点到子树根的距离 
int all;//当前子树大小
ll ans;//最终答案 
int num;//子树每个点的id 
int n,k;
void getroot(int x,int fa)//查找重心 
{
//	printf("getroot:  %d  %d\n",x,fa);
	size[x]=1;//子树x大小 
	int mx=0;// 删掉x后分成最大子树大小
	for(int i=head[x];i!=0;i=E[i].nxt)
	{
		int y=E[i].to;
		if(y==fa||vis[y])continue;//不能访问父亲,和不是该分治后的树的点
		getroot(y,x);
		size[x]+=size[y];//从子节点向父亲结点递推 
		mx=max(mx,size[y]);//x的每个儿子的大小 
	}
	mx=max(mx,all-size[x]);//除了x和他的儿子的数量,即x的另一颗子树 
	if(mx<rs) rt=x,rs=mx;//更新重心 
}
void getdis(int u,int fa,int dist)//得到当前子树里每个点到子树根的距离 
{
	dis[++num]=dist;
	for(int i=head[u];i!=0;i=E[i].nxt)
	{
		int v=E[i].to;
		if(v==fa||vis[v])continue;
		getdis(v,u,dist+E[i].v);
	}
}
void cal(int u,int op,int len)
{
	num=0;getdis(u,0,len);//得到dis数组: u子树所有点到u的距离 
	sort(dis+1,dis+1+num);
	int res=0;
	int l=1,r=num;
	while(l<r)//two-points统计答案
	{
		while(dis[l]+dis[r]>k&&l<r)r--;
		res+=r-l;
		l++;
	} 
	ans+=op*res;
}
 
void dfs(int u)//点分治 
{
	vis[u]=1;//删除了u这个点  分治出子树 
	cal(u,1,0);//计算包含根的答案
	for(int i=head[u];i!=0;i=E[i].nxt)
	{
		int v=E[i].to;
		if(vis[v])continue;
		cal(v,-1,E[i].v);//减去同一个子树合并的不合法答案
		rs=all=size[v];rt=0; 
		getroot(v,0);
		dfs(rt);//递归统计子树的答案 
	}
}
int main()
{
	while(~scanf("%d%d",&n,&k)&&n)
	{
		int x,y,v;
		memset(head,0,sizeof(head));
		memset(vis,0,sizeof(vis));
		num=cnt=ans=0;
		rs=all=n;//初始化 
		for(int i=1;i<=n-1;i++)
			scanf("%d%d%d",&x,&y,&v),add(x,y,v),add(y,x,v);
		getroot(1,0);
		dfs(rt);
		printf("%lld\n",ans);
	}
  	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值