P1600 天天爱跑步
Posted ukcxrtjr
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P1600 天天爱跑步相关的知识,希望对你有一定的参考价值。
题面:https://www.luogu.org/problem/P1600
V1[x] : 以x为LCA的路径的起点的集合。
Spn[x]: 以x为路径起点的路径条数。
V2[x]: 以x为终点的路径的起点集合。
V3[x]: 以x为LCA的路径的终点的集合。
s[x]:以x为终点的路径起点
由于作者不知以什么样的方式引进接下来我们要用到的“桶”的概念,我们暂时先来考虑下面一个问题。
如果对上面的问题理解,那么对于桶这个概念,就能大概理解。
那么,接下来我们进入正题。
对于玩家在树上的路径(u,v)
我们可以对其进行拆分。
拆分成: u ---> LCA(u,v)与LCA(u,v) ---> v两条路径。
对于这一步,因为我们在一开始已经说明是先对每个玩家进行预处理。
所以在这一步我们选择Tarjan版本的LCA会更好一些。因为时间复杂度会更少。
不过,用倍增求LCA对于本题来说也是不会卡的(作者亲测,时间最长的一个点是0.5s左右)。
我们先考虑 u ---> LCA(u,v) 这条路径,这是一条向“上”跑的路径。
对与这条路径上的点i来说,当且仅当deep[i]+w[i] = deep[u]时,u节点对i节点是有贡献的。
那么也就是说,只要符合deep[i]+w[i]的全部是玩家起点的点,就能对i点产生贡献。
所以有下列伪代码:
Dfs1(i)
·prev=bucket[deep[i]+w[i]]
·Dfs1(i.children)
·bucket[deep[i]]+=spn[i]
·ans[i]+=bucket[deep[i]+w[i]]-prev
·for k in V1[i] --bucket[deep[k]]
其中
ans[i]为i节点的最后答案。
Spn与V1数组在文章开头已经声明
Prev为刚访问i节点时bucket[deep[i]+w[i]]里的值。
在这解释一下伪代码中不好理解的最后两条语句。
对于倒数第二条语句,ans[i]加上的其实就是i的子树对i的贡献,为什么?
因为我们在处理好子树之后的,我们已经处理好了对i有影响的节点。
所以我们只要加上先后之间的桶差值就相当于统计了答案。
另外对于最后一条语句,其作用是删去桶中以i为LCA的路径的起点深度桶的值。
因为当我们遍历完i节点的孩子时,对于以i节点为LCA的路径来说。
这条路径上的信息对i的祖先节点是不会有影响的。
所以要将其删去。
在叙述完向上的路径后,我们再来考虑向下的路径,即LCA(u,v) --->v。
对于向下走的路径,我们也思考,在什么条件下,这条路径上的点会获得贡献呢?
很明显的,当 dis(u,v)-deep[v] = w[i]-deep[i] 等式成立的时候,这条路径将会对i点有贡献。
所以,类似的,我们就可以写出第二个Dfs伪代码。
Dfs2(i)
·prev=bucket[w[i]-deep[i]]
·Dfs2(i.children)
·for k in V2[i] ++bucket[dis(s[k],k)-deep[k]]
·ans[i]+=bucket[w[i]-deep[i]]-prev
·for k in V3[i] --bucket[dis(s[k],k)-deep[k]]
其中
·dis(u,v)表示从u节点到v节点的距离
· V3与V2如文章开头所定义。
·关于两条for 语句:第一条是加上以i为终点的路径的贡献。
第二条与第一个Dfs中最后一条语句类似。
对于这道题来说,现在我们主要的思路已经完全讲完了。
但是,对于实现来说,需要注意以下几点。
·对于桶bucket来说,我们在计算的过程中其下标可能是负值,所以我们在操作桶时要将其下标右移N即点数。
·如果一条路径的LCA能观察到这条路上的人,我们还需将该LCA去重。(因为LCA在上下路径上被算了两次)
条件是:if(deep[u] == deep[lca]+w[i])ans[lca]--;
Code:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<ctime>
#include<vector>
using namespace std;
const int N=600005,M=300005;
int n,m,head[N],cnt,anc[N][25],fa[N],dep[N],w[N],ps[N],bucket[N],ans[N];
vector<int> lcas[N],lcat[N],pt[N];
struct Node{
int u,v,nxt;
}edge[N*2];
struct node{
int s,t,lca,dis;
}q[N];
void add(int u,int v){
++cnt;
edge[cnt].u=u;
edge[cnt].v=v;
edge[cnt].nxt=head[u];
head[u]=cnt;
}
void dfs(int u){
anc[u][0]=fa[u];
for(int i=1;i<=22;i++){
anc[u][i]=anc[anc[u][i-1]][i-1];
}
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].v;
if(v!=fa[u]){
fa[v]=u;
dep[v]=dep[u]+1;
dfs(v);
}
}
}
int LCA(int x,int y){
if(dep[x]<dep[y]){
swap(x,y);
}
for(int i=22;i>=0;i--){
if(dep[anc[x][i]]>=dep[y]){
x=anc[x][i];
}
}
if(x==y){
return x;
}
for(int i=22;i>=0;i--){
if(anc[x][i]!=anc[y][i]){
x=anc[x][i];
y=anc[y][i];
}
}
return anc[x][0];
}
int Dis(int x,int y){
return dep[x]+dep[y]-2*dep[LCA(x,y)];
}
void dfs1(int u){
int pre=bucket[dep[u]+w[u]+M];
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].v;
if(v!=fa[u]){
dfs1(v);
}
}
bucket[dep[u]+M]+=ps[u];
ans[u]+=bucket[dep[u]+w[u]+M]-pre;
for(int i=0;i<lcas[u].size();i++){
--bucket[dep[lcas[u][i]]+M];
}
}
void dfs2(int u){
int pre=bucket[w[u]-dep[u]+M];
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].v;
if(v!=fa[u]){
dfs2(v);
}
}
for(int i=0;i<pt[u].size();i++){
bucket[pt[u][i]+M]++;
}
ans[u]+=bucket[w[u]-dep[u]+M]-pre;
for(int i=0;i<lcat[u].size();i++){
--bucket[lcat[u][i]+M];
}
}
int main(){
int u,v;
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
for(int i=1;i<=n;i++){
scanf("%d",&w[i]);
}
dep[1]=1;
dfs(1);
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
q[i].s=u;
q[i].t=v;
q[i].lca=LCA(u,v);
q[i].dis=Dis(u,v);
ps[u]++;
lcas[q[i].lca].push_back(u);
pt[v].push_back(q[i].dis-dep[v]);
lcat[q[i].lca].push_back(q[i].dis-dep[v]);
}
dfs1(1);
dfs2(1);
for(int i=1;i<=m;i++){
if(dep[q[i].s]==dep[q[i].lca]+w[q[i].lca]){
ans[q[i].lca]--;
}
}
for(int i=1;i<=n;i++){
printf("%d ",ans[i]);
}
printf("
");
return 0;
}
以上是关于P1600 天天爱跑步的主要内容,如果未能解决你的问题,请参考以下文章