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 天天爱跑步的主要内容,如果未能解决你的问题,请参考以下文章

P1600 天天爱跑步

luogu P1600 天天爱跑步 |树上差分+LCA

Luogu P1600 天天爱跑步 树上差分

P1600 天天爱跑步

P1600 天天爱跑步

Luogu P1600 天天爱跑步