加载中…

返回上一页

点分治

概念:

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

给定一棵有 \(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  阅读(60)  评论(0)    收藏  举报