题目链接:https://www.luogu.org/problem/P3806
题目描述
给定一棵有n个点的树
询问树上距离为k的点对是否存在。
输入格式
n,m 接下来n-1条边a,b,c描述a到b有一条长度为c的路径
接下来m行每行询问一个K
数据范围:对于100%的数据n<=10000,m<=100,c<=10000,K<=10000000
输出格式
对于每个K每行输出一个答案,存在输出“AYE”,否则输出”NAY”(不包含引号)
题解
虽说是点分治的模板题,但也加大了难度,多组询问,意味着离线处理,每次分治的时候,对每个答案分别判断一遍,每一层复杂度为O(n*m),达到了1e6,不过还好log(n)层,所以最终也就1e7的复杂度。
变量声明:
- temp数组:记录当前子树所有点到重心的距离
- judge数组:相当于将temp数组离散化,记录某一点到重心的距离存在多少条
- test数据:将k组询问离线处理
如何判断某一长度在树中是否出现?在当前分治层,假设求长度为a的是否存在,对temp进行扫描,已知 temp[i] 存在,只需要查询 a-temp[i]是否存在即可,judge直接判断。因为存在某一点对在同一子树的情况,所以还要用去除这些长度。
设有到重心的距离有a、b,要求长度为 c 的点对是否存在,且a+b=c,分两种情况:
1、当a=b时,存在的点对为 i
2、当a!=b时,存在的点对为 judge[a]*judge[b] (乘法原理)。
需要注意 judge[i] 用过一次后不能再用,需要将 judge[a] 和 judge[b] 置零,避免重复计算。最后,如果是第一次进入当前层,test[i]+=如上答案,如果是排除答案的时候进入,则test[i]-=如上答案。最终,test将保存了,每一种路径的可能性有多少可能选择的点对种数。
代码
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false)
const int N = 20020, INF = 0x7f7f7f7f;
int n,m,head[N*2],num,tot,ans;//tot记录当前子树点数
int dis[N],flag[N],temp[N];
//dis记录子树每一点到根节点的距离,flag用于删除根节点,temp总汇到根节点的距离
int query[105];
int test[105];//当前路径有几条
int size[N],Max[N],root;
struct edge
{
int next,to,len;
} G[N*2];
void add(int from,int to,int len)
{
G[++num].next=head[from];
G[num].to=to;
G[num].len=len;
head[from]=num;
}
inline void input(void)
{
for (int i=1; i<n; i++)
{
int x,y,v;
cin>>x>>y>>v;
add(x,y,v), add(y,x,v);
}
}
inline void dp(int fa,int cur)//求树的重心
{
size[cur] = 1, Max[cur] = 0;
for (int i=head[cur]; i; i=G[i].next)
{
int v = G[i].to;
if ( flag[v] || v == fa ) continue;
dp(cur,v);
size[cur] += size[v];
Max[cur] = max( Max[cur], size[v] );
}
Max[cur] = max( Max[cur], tot - size[cur] );
if ( Max[root] > Max[cur] ) root = cur;
}
inline void dfs(int fa,int cur)
{
temp[ ++temp[0] ] = dis[cur];
for (int i=head[cur]; i; i=G[i].next)
{
int v = G[i].to;
if ( v == fa || flag[v] ) continue;
dis[v] = dis[cur] + G[i].len;
dfs(cur,v);
}
}
const int inf=10000007;
int judge[inf];
void calc(int x,int len,bool f2)
{
dis[x] = len;
temp[0] = 0;//temp[0]记录temp数组的长度
dfs(0,x);
for(int i=0; i<m; i++)
{
for(int j=1; j<=temp[0]; j++)
if(temp[j]<inf)
judge[temp[j]]++;
int len=query[i];
for(int j=1; j<=temp[0]; j++)
{
int len2=len-temp[j];
if(len2<0)continue;
if(judge[len2])
{
if(f2)
{
if(len2!=temp[j])
test[i]+=judge[len2]*judge[temp[j]];
else
test[i]+=judge[len2]*(judge[len2]-1)/2;
}
else
{
if(len2!=temp[j])
test[i]-=judge[len2]*judge[temp[j]];
else
test[i]-=judge[len2]*(judge[len2]-1)/2;
}
judge[len2]=0;
judge[temp[j]]=0;
}
}
for(int j=1;j<=temp[0];j++)
if(temp[j]<inf)
judge[temp[j]]=0;
}
}
inline void divide(int x)
{
flag[x] = true;//删去根节点
calc(x,0,true);
//cout<<"ans="<<ans<<"\n";
for (int i=head[x]; i; i=G[i].next)
{
int y = G[i].to;
if ( flag[y] ) continue;
calc(y,G[i].len,false);//点对在同一子树的情况
tot = size[y], root = 0;
dp(0,y);
divide(root);
}
}
inline void reset(void)
{
num = 0;
memset( head, 0, sizeof head );
memset( flag, 0, sizeof flag );
ans = 0, tot = n;
root = 0, Max[0] = INF;
}
int main(void)
{
IOS;
cin>>n>>m;
reset();
input();
for(int i=0; i<m; i++)
cin>>query[i];
dp(0,1);
divide(root);
for(int i=0; i<m; i++)
if(test[i])
cout<<"AYE\n";
else cout<<"NAY\n";
return 0;
}