加载中…

返回上一页

点分治

概念:

点分治,是一个用来求树上距离的算法.

给定一棵有 \(n\) 个点的树,询问树上距离为 \(k\) 的点对是否存在.

这时,我们应该如何求呢?

DFS枚举子树?

显然会超时.

这样,我们就引入了这个求树上距离的强大算法.

首先,我们需要寻找一个合适的根.

那么这个根如何去找呢?我们可以用一个类树形dp来做.

inline void getrt(ll x,ll fa)
{//获取最佳的根节点
	siz[x]=1/*子树大小*/;dp[x]=0;
	for(rll i=0;i<g[x].size();i++)
	{
		rll to=g[x][i].first;
		if(to==fa||vis[to]/*用来防止反复递归父节点*/)
			continue;
		getrt(to,x);
		siz[x]+=siz[to];
		dp[x]=max(dp[x],siz[to]);
	}
	dp[x]=max(dp[x],size-siz[x]);
	if(dp[x]<dp[rt]) rt=x;
}

相信聪明的人可以看到,我们需要一个vis数组来防止重复递归.

下面,重头戏来了:

inline void dfz(ll x)//点分治
{
	vis[x]=1;
	calc(x);
	for(rll i=0;i<g[x].size();i++)
	{
		rll to=g[x][i].first;
		if(vis[to]) continue;
		rt=0;dp[0]=n;
		size=siz[to];
		getrt(to,x);
		dfz(rt);
	}
}

显然,我们需要计算一下树上路径的大小.

那么如何统计呢?

inline void calc(ll x)//calc函数
{
	for(rll i=0;i<g[x].size();i++)
	{
		rll to=g[x][i].first;
		if(vis[to]) continue;
		for(rll j=0;j<=2;j++) num1[j]=0;
		dis[to]=g[x][i].second;
		getdis(to,x);
	}
}

然后,通过getdis函数获取一下距离:

inline void getdis(ll x,ll fa)
{
	for(rll i=0;i<g[x].size();i++)
	{
		rll to=g[x][i].first;
		if(to==fa||vis[to]) continue;
		dis[to]=dis[x]+g[x][i].second;
		getdis(to,x);
	}
}

这样,一个点分治就完成了.

例题:

[1]Luogu P3806【模板】点分治1

题目描述

给定一棵有 \(n\) 个点的树,询问树上距离为 \(k\) 的点对是否存在.

输入格式

第一行两个数 \(n,m\).

第 \(2\) 到第 \(n\) 行,每行三个整数 \(u, v, w\),代表树上存在一条连接 \(u\) 和 \(v\) 边权为 \(w\) 的路径.

接下来 \(m\) 行,每行一个整数 \(k\),代表一次询问.

输出格式

对于每次询问输出一行一个字符串代表答案,存在输出 AYE,否则输出 NAY.

样例输入

2 1
1 2 2
2

样例输出

AYE

提示

数据规模与约定

  • 对于 \(30\%\) 的数据,保证 \(n\leq 100\).
  • 对于 \(60\%\) 的数据,保证 \(n\leq 1000\),\(m\leq 50\) .
  • 对于 \(100\%\) 的数据,保证 \(1 \leq n\leq 10^4\),\(1 \leq m\leq 100\),\(1 \leq k \leq 10^7\),\(1 \leq u, v \leq n\),\(1 \leq w \leq 10^4\).

放标程:

#include<bits/stdc++.h>
#define ll long long
#define rg register
#define maxn 10000001
#define rll rg ll
using namespace std;
ll n,m;
ll rt,siz[maxn];
ll size,dp[maxn]/*当前最大子树节点个数*/;
bool num1[maxn];//本路径上该长度能达到的数字
ll num2[maxn];//非本路径该长度能达到的数字
ll dis[maxn];
ll q[maxn];//当前节点的距离
bool ans[maxn];
#define tot num2[0]
bool vis[maxn];//用来防止反复递归父节点
queue<ll> que;
vector<pair<ll,ll> > g[maxn];
inline void getrt(ll x,ll fa)
{//获取最佳的根节点
	siz[x]=1;dp[x]=0;
	for(rll i=0;i<g[x].size();i++)
	{
		rll to=g[x][i].first;
		if(to==fa||vis[to]/*用来防止反复递归父节点*/)
			continue;
		getrt(to,x);
		siz[x]+=siz[to];
		dp[x]=max(dp[x],siz[to]);
	}
	dp[x]=max(dp[x],size-siz[x]);
	if(dp[x]<dp[rt]) rt=x;
}
inline void getdis(ll x,ll fa)
{
	num2[++tot]=dis[x];
	for(rll i=0;i<g[x].size();i++)
	{
		rll to=g[x][i].first;
		if(to==fa||vis[to]) continue;
		dis[to]=dis[x]+g[x][i].second;
		getdis(to,x);
	}
}
inline void sol(ll x)
{
	for(rll i=0;i<g[x].size();i++)
	{
		rll to=g[x][i].first;
		if(vis[to]) continue;
		tot=0;dis[to]=g[x][i].second;
		getdis(to,x);
		for(rll j=1;j<=tot;j++)
			for(rll k=1;k<=m;k++)
				if(q[k]>=num2[j])
					ans[k]|=num1[q[k]-num2[j]];
		for(rll j=1;j<=tot;j++)
			que.push(num2[j]),num1[num2[j]]=1;
	}
	while(!que.empty()) num1[que.front()]=0,que.pop();
}
inline void dfz(ll x)//点分治
{
	vis[x]=num1[0]=1;
	sol(x);
	for(rll i=0;i<g[x].size();i++)
	{
		rll to=g[x][i].first;
		if(vis[to]) continue;
		rt=0;dp[0]=n;
		size=siz[to];
		getrt(to,x);
		dfz(rt);
	}
}
int main()
{
	ios::sync_with_stdio(0);
	cin>>n>>m;
	dp[0]=size=n;
	for(rll i=1,u,v,w;i<n;i++)
	{
		cin>>u>>v>>w;
		g[u].push_back(make_pair(v,w));
		g[v].push_back(make_pair(u,w));
	}
	getrt(1,0);
	for(rll i=1;i<=m;i++) cin>>q[i];
	dfz(rt);
	for(rll i=1;i<=m;i++) cout<<(ans[i]?"AYE":"NAY")<<endl;
	return 0;
}

[2]Bzoj2152 聪聪可可

题目描述

聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画n个“点”,并用n-1条“边”把这n个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点(当然他们选点时是看不到这棵树的),如果两个点之间所有边上数的和加起来恰好是3的倍数,则判聪聪赢,否则可可赢。聪聪非常爱思考问题,在每次游戏后都会仔细研究这棵树,希望知道对于这张图自己的获胜概率是多少。现请你帮忙求出这个值以验证聪聪的答案是否正确。

输入格式

输入的第1行包含1个正整数n。
后面n-1行,每行3个整数x、y、w,表示x号点和y号点之间有一条边,上面的数是w。

输出格式
以即约分数形式输出这个概率(即“a/b”的形式,其中a和b必须互质。如果概率为1,输出“1/1”)。

样例

样例输入

5
1 2 1
1 3 2
1 4 1
2 5 3

样例输出

13/25
#include<bits/stdc++.h>
#define ll long long
#define rg register
#define maxn 100001
#define rll rg ll
using namespace std;
ll n,m,a,b,c;
ll rt,siz[maxn];
ll size,dp[maxn]/*当前最大子树节点个数*/;
ll num1[maxn],num2[maxn];
ll dis[maxn];
ll ans;
bool vis[maxn];//用来防止反复递归父节点
vector<pair<ll,ll> > g[maxn];
inline void getrt(ll x,ll fa)
{//获取最佳的根节点
	siz[x]=1;dp[x]=0;
	for(rll i=0;i<g[x].size();i++)
	{
		rll to=g[x][i].first;
		if(to==fa||vis[to]/*用来防止反复递归父节点*/)
			continue;
		getrt(to,x);
		siz[x]+=siz[to];
		dp[x]=max(dp[x],siz[to]);
	}
	dp[x]=max(dp[x],size-siz[x]);
	if(dp[x]<dp[rt]) rt=x;
}
inline void getdis(ll x,ll fa)
{
	/*这里根据实际情况更改*/
	num1[dis[x]%3]++;
	/*这里根据实际情况更改*/
	for(rll i=0;i<g[x].size();i++)
	{
		rll to=g[x][i].first;
		if(to==fa||vis[to]) continue;
		dis[to]=dis[x]+g[x][i].second;
		getdis(to,x);
	}
}
inline void sol(ll x)//calc函数
{
	for(rll i=0;i<g[x].size();i++)
	{
		rll to=g[x][i].first;
		if(vis[to]) continue;
		for(rll j=0;j<=2;j++) num1[j]=0;
		dis[to]=g[x][i].second;
		getdis(to,x);
		/*这里根据实际情况更改*/
		ans+=2*num2[0]*num1[0]+2*num2[1]*num1[2]+2*num2[2]*num1[1]+2*num1[0];
		for(rll j=0;j<=2;j++) num2[j]+=num1[j];
		/*这里根据实际情况更改*/
	}

}
inline void dfz(ll x)//点分治
{
	vis[x]=1;
	sol(x);
	for(rll i=0;i<=2;i++) num2[i]=0;
	for(rll i=0;i<g[x].size();i++)
	{
		rll to=g[x][i].first;
		if(vis[to]) continue;
		rt=0;dp[0]=n;
		size=siz[to];
		getrt(to,x);
		dfz(rt);
	}
}
inline ll gcd(ll x,ll y)
{
	return (!y)?x:gcd(y,x%y);
}
int main()
{
	ios::sync_with_stdio(0);
	cin>>n;
	dp[0]=size=n;
	for(rll i=1,u,v,w;i<n;i++)
	{
		cin>>u>>v>>w;w%=3;
		g[u].push_back(make_pair(v,w));
		g[v].push_back(make_pair(u,w));
	}
	getrt(1,0);
	dfz(rt);
	a=n+ans;b=n*n;c=gcd(a,b);
	cout<<a/c<<'/'<<b/c;
	return 0;
}
posted @ 2022-06-18 11:36  1Liu  阅读(76)  评论(0)    收藏  举报