点分治入门

点分治就是树上的分治,也就是处理把分治强行搬到树上的毒瘤问题。

算法流程

引入:[国家集训队]聪聪可可 (各大OJ搜索即可)

题意:给一棵树,随机选两个点(可以相同),求距离为3的倍数的概率。

显然求长度是3的倍数的路径数即可。

首先是分治,那我们考虑一个子问题,即一棵树。

然而树上没有中点。如果直接分,给条链就挂了。

所以我们yy一个中点:重心

重心定义是以它为根的子树size的最大值最小。

因为是树,所以至少有两个子树。又是均分的,可以保证log

我们先找出重心,然后乱搞求过重心的路径数。

然后递归就可以解决。

实现

口胡和实现难度差别最大的算法。

首先需要一个findroot来找重心

开一个cut数组记录被砍掉的节点,相当于移出游戏 。

用solve控制流程。进入solve后,马上砍掉root

然后马上用calc计算当前子问题

calc中用dfs处理子树信息并顺便求出大小,然后整合答案并更新。注意清零。

此过程中一定不要吝啬空间,可以重新开个数组记录修改的地方,算完后再依次还原。最好开临时数组记录当前子树并和之前统计答案,算完后合并。千万不要memset,用for也要仔细算复杂度。

当然本题很友好,只有三个变量。

回到solve,用dfs中求出的大小找子树中的重心并递归

代码

风格可能和大众不同,仅供参考

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#define MAXN 20005
#define MAXM 40005
#define INF 0x7fffffff
using namespace std;
inline int read()
{
	int ans=0;
	char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
int n;
struct edge
{
	int u,v,w;
}e[MAXM];
int head[MAXN],nxt[MAXM],cnt;
void addnode(int u,int v,int w)
{
	e[++cnt]=(edge){u,v,w};
	nxt[cnt]=head[u];
	head[u]=cnt;
}
bool cut[MAXN];
int ans;
int root,siz[MAXN],maxp[MAXN];
void findroot(int u,int f,int sum)
{
	siz[u]=1,maxp[u]=0;
	for (int i=head[u];i;i=nxt[i])
		if (e[i].v!=f&&!cut[e[i].v])
		{
			findroot(e[i].v,u,sum);
			siz[u]+=siz[e[i].v];
			maxp[u]=max(maxp[u],siz[e[i].v]);
		}
	if (sum-siz[u]>maxp[u]) maxp[u]=sum-siz[u];
	if (maxp[u]<maxp[root]) root=u;
}
int tot[3],tt[3];
int dfs(int u,int f,int s)
{
	int ans=1;
	++tt[s];
	for (int i=head[u];i;i=nxt[i])
		if (e[i].v!=f&&!cut[e[i].v])
			ans+=dfs(e[i].v,u,(s+e[i].w)%3);
	return ans;
}
int sum[MAXN];
void calc()
{
	tot[0]=1,tot[1]=tot[2]=0;
	for (int i=head[root];i;i=nxt[i])
		if (!cut[e[i].v])
		{
			tt[0]=tt[1]=tt[2]=0;
			sum[e[i].v]=dfs(e[i].v,0,e[i].w);
			ans+=tot[0]*tt[0]+tot[1]*tt[2]+tot[2]*tt[1];
			tot[0]+=tt[0],tot[1]+=tt[1],tot[2]+=tt[2];
		}
}
void solve()
{
	cut[root]=1;
	calc();
	for (int i=head[root];i;i=nxt[i])
		if (!cut[e[i].v])
		{
			maxp[root=0]=INF;
			findroot(e[i].v,0,sum[e[i].v]);
			solve();
		}
}
int gcd(const int& a,const int& b){return b? gcd(b,a%b):a;}
void print(int a,int b)
{
	int g=gcd(a,b);
	a/=g,b/=g;
	printf("%d/%d",a,b);
}
int main()
{
	n=read();
	for (int i=1;i<n;i++)
	{
		int u,v,w;
		u=read(),v=read(),w=read()%3;
		addnode(u,v,w),addnode(v,u,w);
	}
	maxp[0]=INF;
	findroot(1,0,n);
	solve();
	print(2*ans+n,n*n);
	return 0;
}

[IOI2011]Race

题意:给定一棵树和k,求长为k的路径中最小的边数。 k ≤ 1000000 k \leq 1000000 k1000000

其它一样,主要是calc

开个1e6的数组dis记录长为i的路径最小边数

用tdis记录当前子树,dfs时更新

用q和tq记录dis和tdis修改的下标,算完后还原成INF

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#define MAXN 200005
#define MAXM 400005
#define MAXV 1000005
#define INF 0x3f3f3f3f
using namespace std;
inline int read()
{
	int ans=0,f=1;
	char c=getchar();
	while (!isdigit(c)) 
	{
		if (c=='-') f=-1;
		c=getchar();
	}
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return f*ans;
}
int n,k;
struct edge{int u,v,w;}e[MAXM];
int head[MAXN],nxt[MAXM],cnt;
void addnode(int u,int v,int w)
{
	e[++cnt]=(edge){u,v,w};
	nxt[cnt]=head[u];
	head[u]=cnt;
}
bool cut[MAXN];
int root;
int siz[MAXN],maxp[MAXN];
void findroot(int u,int f,int s)
{
	siz[u]=1,maxp[u]=0;
	for (int i=head[u];i;i=nxt[i])
		if (!cut[e[i].v]&&e[i].v!=f)
		{
			findroot(e[i].v,u,s);
			siz[u]+=siz[e[i].v];
			maxp[u]=max(maxp[u],siz[e[i].v]);
		}
	if (s-siz[u]>maxp[u]) maxp[u]=s-siz[u];
	if (maxp[u]<maxp[root]) root=u;
}
int dis[MAXV],tdis[MAXV],sum[MAXN];
int q[MAXN],top,tq[MAXN],tt;
int dfs(int u,int f,int s,int d)
{
	int ans=1;
	if (s<=k) tdis[tq[++tt]=s]=min(tdis[s],d);
	for (int i=head[u];i;i=nxt[i])
		if (!cut[e[i].v]&&e[i].v!=f)
			ans+=dfs(e[i].v,u,s+e[i].w,d+1);
	return ans;
}
int ans=INF;
void calc()
{
	dis[0]=top=0;
	for (int i=head[root];i;i=nxt[i])
		if (!cut[e[i].v])
		{
			tt=0;
			sum[e[i].v]=dfs(e[i].v,0,e[i].w,1);
			for (int i=1;i<=tt;i++)	ans=min(ans,tdis[tq[i]]+dis[k-tq[i]]);
			for (int i=1;i<=tt;i++) dis[tq[i]]=min(dis[tq[i]],tdis[tq[i]]),q[++top]=tq[i];
			for (int i=1;i<=tt;i++) tdis[tq[i]]=INF;	
		}
	for (int i=1;i<=top;i++) dis[q[i]]=INF;
}
void solve()
{
	cut[root]=true;
	calc();
	for (int i=head[root];i;i=nxt[i])
		if (!cut[e[i].v])
		{
			maxp[root=0]=INF;
			findroot(e[i].v,0,sum[e[i].v]);
			solve();
		}
}
int main()
{
	n=read(),k=read();
	for (int i=1;i<n;i++)
	{
		int u,v,w;
		u=read()+1,v=read()+1,w=read();
		addnode(u,v,w),addnode(v,u,w);
	}
	maxp[root=0]=INF;
	findroot(1,0,n);
	memset(dis,0x3f,sizeof(dis)+sizeof(tdis));
	solve();
	printf("%d\n",(ans==INF? -1:ans));
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值