集训day9上午各种神仙分治真心听不懂,自己颓着把点分治学了,简单(并不)写一下再加深一下理解吧。
姑且将其命名为小捞鼠。
考虑路径问题,假设一条满足条件的路径经过点 1 ,那么会出现两种情况:
1.这条路径在 x 的一个子鼠里(以 1 为端点)
2.在 1 的两个不同的子鼠里。
这时候一个想法就会是从根节点出发进行dfs,分别处理紫薯子树紫鼠子鼠的答案。
等等,都说了路径经过点 1 可能产生两种情况,莫非是要分类讨论?小捞鼠这么友善肯定不会让我们分类讨论的。
并不会,对于情况 2,我们可以果断直接分治,假设要求出点 4 到点 7 的路径,我们可以分别处理 4 和 7 ,用dis[]表示当前结点到根节点 1 的路径长度, 则 4 到 7 的路径长即为dis[4]+dis[7]。
以下为求重心代码,帮助理解:(sum代表以父节点为根的子鼠大小)
void getroot(int id,int fa)
{
size[id]=1;//初始化鼠大小
maxp[id]=0;//记录最大子鼠大小
for(int i=head[id];i;i=nxt[i])//遍历连边
{
if(to[i]==fa||vis[to[i]])//如果回到父节点或已查询就跳过
continue;
getroot(to[i],id);//递归
size[id]+=size[to[i]];//加上子鼠大小
maxp[id]=max(size[to[i]],maxp[id]);//更新最肥子鼠
}
maxp[id]=max(maxp[id],sum-size[id]);//与父节点的另一子鼠比较
if(maxp[id]<maxp[root])//若该子鼠小,更新重心
root=id;
}
于是来到了最重要的分治部分,这个部分有点难懂我要结合一下真正的题干:
题目描述
给定一棵有n个点的树
询问树上距离为k的点对是否存在。
(多次询问&&可离线)
求的不是某条路径,而是从所有可能的路径中统计答案。
这好办啊,先离线所有的询问,将k存储在一个query数组里,枚举所有可能路径,每次扫一遍query数组,若路径长度=k就标记该次询问,最后输出标记就可以了。
那怎么用分治实现呢?
从重心向下枚举子鼠根节点,根节点又会形成子鼠为v1,v2……vt, 我们从第一个子鼠v1开始,求出每棵子鼠的dis[]
假设当前处理的子鼠为vi, 对离线记录的每个询问, 枚举v1到vi-1的dis与当前子鼠的dis的和, 若相等就标记
路径加和后将这棵子鼠的dis与前面的dis一起保存于一个数组, 当这个根节点的子鼠查询完后清空这个数组
然后对其他子鼠进行分治,可以保证所有路径都被枚举过。
END
有讲的不清楚的地方随时私信本蒟蒻,下方的代码给出了详细注释
die码如下:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define inf 10000000
#define maxn 100010
using namespace std;
int n,m;
int cnt,nxt[maxn<<1],to[maxn<<1],head[maxn],val[maxn<<1];//链前存边用的
int maxp[maxn],size[maxn],dis[maxn],rem[maxn],tot;//maxp求重心时保存最大子鼠
int vis[maxn],test[inf],ju[inf],q[maxn];//vis为标记数组,test存储每个询问的结果,ju[i]表示到根距离为i的路径是否存在
int query[1010];//存储询问
int sum,root;
void addedge(int x,int y,int v)
{
nxt[++cnt]=head[x];
to[cnt]=y;
head[x]=cnt;
val[cnt]=v;
}
void getroot(int id,int fa)//求重心,详细注释见上文
{
size[id]=1;
maxp[id]=0;
for(int i=head[id];i;i=nxt[i])
{
if(to[i]==fa||vis[to[i]])
continue;
getroot(to[i],id);
size[id]+=size[to[i]];
maxp[id]=max(size[to[i]],maxp[id]);
}
maxp[id]=max(maxp[id],sum-size[id]);
if(maxp[id]<maxp[root])
root=id;
}
void getdis(int id,int fa)
{
rem[++rem[0]]=dis[id];
for(int i=head[id];i;i=nxt[i])
{
if(to[i]==fa||vis[to[i]])
continue;
dis[to[i]]=dis[id]+val[i];
getdis(id,to[i]);//递归求dis
}
}
void cacl(int id)
{
int now=0;
for(int i=head[id];i;i=nxt[i])
{
if(vis[to[i]])
continue;
rem[0]=0;
dis[to[i]]=val[i];
getdis(to[i],id);//求solve函数的子鼠的所有子鼠的dis值
for(int j=rem[0];j;j--)
for(int k=1;k<=m;++k)
if(query[k]>=rem[j])
test[k]|=ju[query[k]-rem[j]];//与询问比较,记录答案
for(int j=rem[0];j;j--)
{
q[++now]=rem[j];
ju[rem[j]]=1;
}
}
for(int i=1;i<=now;i++)
ju[q[i]]=0;//为下一棵子鼠清空
}
void solve(int id)
{
vis[id]=ju[0]=1;
cacl(id);//处理该子鼠
for(int i=head[id];i;i=nxt[i])//分治子鼠
{
if(vis[to[i]])
continue;
sum=size[to[i]];
maxp[root=0]=inf;
getroot(to[i],0);//找下个鼠的重心
solve(root);//处理下个子鼠
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
int u,v,dis;
scanf("%d%d%d",&u,&v,&dis);
addedge(u,v,dis);
addedge(v,u,dis);
}
for(int i=1;i<=m;i++)
scanf("%d",&query[i]);
maxp[root]=sum=n;//这一行和下一行,找整棵鼠的重心 ,我的鼠终于不会扣篮滑倒了
getroot(1,0);
solve(root);//操作
for(int i=1;i<=m;i++)
if(test[i])//愉快地输出答案
printf("AYE
");
else
printf("NAY
");
return 0;
}