点分治
概念:
点分治,是一个用来求树上距离的算法.
给定一棵有 \(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;
}
--END--

浙公网安备 33010602011771号
我的博客: 𝟷𝙻𝚒𝚞
本文链接: https://www.cnblogs.com/1Liu/articles/16387987.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!