题解 P1600 【天天爱跑步】

· · 题解

首先声明这不是一篇算法独特的题解,仍然是“LCA+桶+树上差分”,但这篇题解是为了让很多很多看了很多题解仍然看不懂的朋友们看懂的,其中就包括我,我也在努力地把解题的“思维过程”呈现出来,希望能帮助到别人。实在是佩服那些考场AC的大牛,再次向你们献上敬意!

原文链接(https://www.cnblogs.com/lfyzoi/p/10221884.html)
1. 第一步
#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;
}
2. 第二步

大概分析一下,m个玩家对应m条路径,有了起点和终点的 lca 后,如果我们模拟这个过程:

直觉

尝试

转换

深入分析

3. 第三步

如何统计子树贡献

还要考虑一种情况

代码说明

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;
}

一些重要补充

4. 结束

我不知道自己说清楚没有,但愿大家不要拍砖头!下面是完整代码

#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;
}