fa[][]
数组(fa[x][i]
表示 \(x\) 结点的 \(2^i\) 代祖先是谁)和deep[]数组(deep[x]
表示结点 \(x\) 在树中的深度)#include<bits/stdc++.h>
using namespace std;
const int SIZE=300000;
int n, m, tot, h[SIZE], deep[SIZE], fa[SIZE][20], w[SIZE]; //w[i]表示i结点出现观察员的时间
struct edge
{
int to, next;
}E[SIZE*2], e1[SIZE*2], e2[SIZE*2]; //边集数组e1,e2留待备用
void add(int x, int y) //加边函数
{
E[++tot].to=y;
E[tot].next=h[x];
h[x]=tot;
}
void dfs1(int x) //dfs的过程中完成“建树”,预处理fa[][]数组, 计算deep[]数组
{
for(int i=1; (1<<i)<=deep[x]; i++)
fa[x][i]=fa[fa[x][i-1]][i-1]; //x的2^i代祖宗就是x的2^{i-1}代祖宗的2^{i-1}代祖宗
for(int i=h[x]; i; i=E[i].next)
{
int y=E[i].to;
if(y==fa[x][0]) continue; //如果y是父结点,跳过
fa[y][0]=x;
deep[y]=deep[x]+1;
dfs1(y);
}
}
int get_lca(int x, int y) //计算x和y的最近公共祖先
{
if(x==y) return x; //没有这一行,遇到 lca(x, x) 这样的询问时会挂掉
if(deep[x]<deep[y]) swap(x, y); //保持x的深度大于y的深度
int t=log(deep[x]-deep[y])/log(2);
for(int i=t; i>=0; i--) //x向上跳到和y同样的深度
{
if(deep[fa[x][i]]>=deep[y])
x=fa[x][i];
if(x==y)
return x;
}
t=log(deep[x])/log(2);
for(int i=t; i>=0; i--) //x和y一起向上跳
{
if(fa[x][i]!=fa[y][i])
x=fa[x][i], y=fa[y][i];
}
return fa[x][0];
}
int main() //先把主函数写上一部分
{
scanf("%d%d", &n, &m);
for(int i=1; i<n; i++)
{
int u, v;
scanf("%d%d", &u, &v);
add(u, v);
add(v, u);
}
deep[1]=1;
fa[1][0]=1;
dfs1(1);
for(int i=1; i<=n; i++) scanf("%d", &w[i]);
/////////////////////////////////////////////////////////////
////////////////////////未完待续///////////////////////////
/////////////////////////////////////////////////////////////
return 0;
}
大概分析一下,m个玩家对应m条路径,有了起点和终点的 lca 后,如果我们模拟这个过程:
直觉
尝试
转换
深入分析
我们可以得出结论:当起点 $ s_i $ 满足 $ deep[s_i]=w[P]+deep[P] $时,起点 \(s_i\)会为 \(P\) 观察员做一个贡献(运动员从\(s_i\)出发,可以被\(P\)处的观察员在\(w[P]\)秒看到)
如果 \(P\) 是在从 \(LCA\) 到 \(t_i\) 的路上,如下图:
如何统计子树贡献
还要考虑一种情况
代码说明
e1,tot1,h1,add1
是使用链式前向星的方法存储每个结点作为终点对应的路径集合e2,tot2,h2,add2
是使用链式前向星的方法存储每个结点作为LCA对应的路径集合b1,b2
是两组桶,分别用于上行阶段和下行阶段的贡献统计js[SIZE]
用于统计以每个结点作为起点的路径条数dist[SIZE], s[SIZE], t[SIZE]
用于统计m条路径对应的长度,起点和终点信息ans[SIZE]
存储最后输出的答案,是每个结点观察员看到的人数int tot1, tot2, h1[SIZE], h2[SIZE];
void add1(int x, int y)
{
e1[++tot1].to=y;
e1[tot1].next=h1[x];
h1[x]=tot1;
}
void add2(int x, int y)
{
e2[++tot2].to=y;
e2[tot2].next=h2[x];
h2[x]=tot2;
}
int b1[SIZE*2], b2[SIZE*2], js[SIZE], dist[SIZE], s[SIZE], t[SIZE], ans[SIZE];
void dfs2(int x)
{
int t1=b1[w[x]+deep[x]], t2=b2[w[x]-deep[x]+SIZE]; //递归前先读桶里的数值,t1是上行桶里的值,t2是下行桶的值
for(int i=h[x]; i; i=E[i].next) //递归子树
{
int y=E[i].to;
if(y==fa[x][0]) continue;
dfs2(y);
}
b1[deep[x]]+=js[x]; //上行过程中,当前点作为路径起点产生贡献,入桶
for(int i=h1[x]; i; i=e1[i].next) //下行过程中,当前点作为路径终点产生贡献,入桶
{
int y=e1[i].to;
b2[dist[y]-deep[t[y]]+SIZE]++;
}
ans[x]+=b1[w[x]+deep[x]]-t1+b2[w[x]-deep[x]+SIZE]-t2; //计算上、下行桶内差值,累加到ans[x]里面
for(int i=h2[x]; i; i=e2[i].next) //回溯前清除以此结点为LCA的起点和终点在桶内产生的贡献,它们已经无效了
{
int y=e2[i].to;
b1[deep[s[y]]]--; //清除起点产生的贡献
b2[dist[y]-deep[t[y]]+SIZE]--; //清除终点产生的贡献
}
}
int main()
{
////////////////重复部分跳过////////////
////////////////文末提供完整代码////////
for(int i=1; i<=m; i++) //读入m条询问
{
scanf("%d%d", &s[i], &t[i]);
int lca=get_lca(s[i], t[i]); //求LCA
dist[i]=deep[s[i]]+deep[t[i]]-2*deep[lca]]; //计算路径长度
js[s[i]]++; //统计以s[i]为起点路径的条数,便于统计上行过程中该结点产生的贡献
add1(t[i], i); //第i条路径加入到以t[i]为终点的路径集合中
add2(lca, i); //把每条路径归到对应的LCA集合中
if(deep[lca]+w[lca]==deep[s[i]]) ans[lca]--; //见下面的解释
}
dfs2(1); //dfs吧!
for(int i=1; i<=n; i++) printf("%d ", ans[i]);
return 0;
}
一些重要补充
if(deep[lca]+w[lca]==deep[s[i]]) ans[lca]--;
好在这种情况很容易发现,我们提前预测到,对相应的结点进行ans[x]--
即可。
此外,在使用第二个桶时,下标是w[x]-deep[x]
会成为负数,所以使用第二个桶时,下标统一+SIZE
,向右平移一段区间,防止下溢。
我不知道自己说清楚没有,但愿大家不要拍砖头!下面是完整代码
#include<bits/stdc++.h>
using namespace std;
const int SIZE=300000;
int n, m, tot, h[SIZE], deep[SIZE], fa[SIZE][20], w[SIZE];
struct edge
{
int to, next;
}E[SIZE*2], e1[SIZE*2], e2[SIZE*2];
void add(int x, int y)
{
E[++tot].to=y;
E[tot].next=h[x];
h[x]=tot;
}
int tot1, tot2, h1[SIZE], h2[SIZE];
void add1(int x, int y)
{
e1[++tot1].to=y;
e1[tot1].next=h1[x];
h1[x]=tot1;
}
void add2(int x, int y)
{
e2[++tot2].to=y;
e2[tot2].next=h2[x];
h2[x]=tot2;
}
void dfs1(int x)
{
for(int i=1; (1<<i)<=deep[x]; i++)
fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=h[x]; i; i=E[i].next)
{
int y=E[i].to;
if(y==fa[x][0]) continue;
fa[y][0]=x;
deep[y]=deep[x]+1;
dfs1(y);
}
}
int get_lca(int x, int y)
{
if(x==y) return x;
if(deep[x]<deep[y]) swap(x, y);
int t=log(deep[x]-deep[y])/log(2);
for(int i=t; i>=0; i--)
{
if(deep[fa[x][i]]>=deep[y])
x=fa[x][i];
if(x==y)
return x;
}
t=log(deep[x])/log(2);
for(int i=t; i>=0; i--)
{
if(fa[x][i]!=fa[y][i])
x=fa[x][i], y=fa[y][i];
}
return fa[x][0];
}
int b1[SIZE*2], b2[SIZE*2], js[SIZE], dist[SIZE], s[SIZE], t[SIZE], l[SIZE], ans[SIZE];
void dfs2(int x)
{
int t1=b1[w[x]+deep[x]], t2=b2[w[x]-deep[x]+SIZE];
for(int i=h[x]; i; i=E[i].next)
{
int y=E[i].to;
if(y==fa[x][0]) continue;
dfs2(y);
}
b1[deep[x]]+=js[x];
for(int i=h1[x]; i; i=e1[i].next)
{
int y=e1[i].to;
b2[dist[y]-deep[t[y]]+SIZE]++;
}
ans[x]+=b1[w[x]+deep[x]]-t1+b2[w[x]-deep[x]+SIZE]-t2;
for(int i=h2[x]; i; i=e2[i].next)
{
int y=e2[i].to;
b1[deep[s[y]]]--;
b2[dist[y]-deep[t[y]]+SIZE]--;
}
}
int main()
{
scanf("%d%d", &n, &m);
for(int i=1; i<n; i++)
{
int u, v;
scanf("%d%d", &u, &v);
add(u, v);
add(v, u);
}
deep[1]=1;
fa[1][0]=1;
dfs1(1);
for(int i=1; i<=n; i++) scanf("%d", &w[i]);
for(int i=1; i<=m; i++)
{
scanf("%d%d", &s[i], &t[i]);
int lca=get_lca(s[i], t[i]);
dist[i]=deep[s[i]]+deep[t[i]]-2*deep[lca];
js[s[i]]++;
add1(t[i], i);
add2(lca, i);
if(deep[lca]+w[lca]==deep[s[i]]) ans[lca]--;
}
dfs2(1);
for(int i=1; i<=n; i++) printf("%d ", ans[i]);
return 0;
}
原文:https://www.cnblogs.com/lfyzoi/p/10221884.html