NOIP2016提高组D1T2天天爱跑步(lca+树上差分)

D1T2 天天爱跑步
地址:天天爱跑步Vijos
题解:
这道题我觉得应该与T3换一个位置,难度显然有问题的说。
在树上一个点跑到另一个点的位置显然通过路程为,s->lca(s,t)->t.我们因此就可以拆路径,拆成向上的s->lca(s,t)与向下的lca(s,t)->t.

然后我们将向上和向下的两段路径分开来讨论。

向上的时候,每个点观察到的点的深度实际就是观察点的深度加上观察时间(即走这么一段路径所花的时间),当然这个点还要在观察点的子树里才能向上走时经过观察点。即:

deep[观察点] + 观察时间[观察点] = deep[s]

我们就可以用子树和的方法,在s点++,在lca- -(避免这个路径已经在观察点前结束了,但依旧记录进观察点所观察到的数目里,所以要- - ),在我的代码中的- -操作旁还有关于具体操作的注释。因为我们记录的是某一深度中的起点数,这会将之前不同子树的结果也记录进去,但依旧是上面的观察点需要的内容,为了避免其他子树的结果,我们在遍历此子树时先记录原来的值,在遍历后用遍历后的值减去原来的值就是结果。当然这些操作的前提就是观察点的深度加上观察时间没有超过树的最大深度。


向下的时候也差不多。不过每个路径的终点要被观察到的话,就有总路径长度(走整个路径的时间)-观察时间 等于 路径终点的深度减去观察点的深度。因为观察点要观察到此点,显然在以此点为终点的路径上,那么起点到观察点的时间就等于观察时间,就是总路径时间要去掉从观察点走到终点的时间刚好是观察时间。将有包含观察点的所有项都移到等式的一边,即:

deep[t] - 路径长度[s->t] = deep[观察点] - 观察时间[观察点]

但这里与上面向下走的时候有些不同,深度可能会产生负数(是合法的具体在下面解释),我们的数组不能存以负数为下标的值,因此我们将这个操作中的深度都加上300000(最大可能的n),这样能保证计算出的结果均为正数。

但对于某些人来说这里依然用原先路径s->t来解释这里的等式,而不是用单纯的lca->t来推导感到一些不理解,其实这里的等式是可以用lca->t来推导的,我上面只是粗略的介绍一下(公式内容上的字面理解)。对我来说其实推导过程是这样的:

我们在走lca->t这条向下的路时,时间已经过去了一部分,即s->lca的路径长度(通过时间)。如果观察点想要观察到从lca开始的路径,那么剩下的时间恰好等于lca到观察点的距离,即deep[观察点]-deep[lca]。我们有等式:

观察时间[观察点] - 路径长度[s->lca] = deep[观察点] - deep[lca]

即:

deep[lca] - 路径长度[s->lca] = deep[观察点] - 观察时间[观察点]

此处负数是合法的因为,对lca这个数来说,它原先的路径是从下往上的,所以不存在是从根之上的节点向下走来的。

当然这个等式的实现是从上往下的,与之前的从下往上传递有些出入,为了统一相对来说的方法(想的话有些麻烦,当然要是可以实现这个等式的话,写写也没问题,但我的标程里不是这样),我们在等式的左侧同时加上deep[t]-deep[lca]与减去lca->t的路径长度,因为是一条链,所以两者其实相等,并不影响等式。因此我们就可以得到:

deep[t] - deep[lca] + deep[lca] - 路径长度[s->lca] - 路径长度[lca -> t] 
= 
deep[观察点] - 观察时间[观察点]

即:

deep[t] - 路径长度[s->t] = deep[观察点] - 观察时间[观察点]

那么t也在以观察点为根的子树中,就可以用相同的方法处理了。
最后时间复杂度其实是用倍增求lca的复杂度O(nlogn)

代码:

#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
#define ll long long
#define N 300005
inline int read(){
    int x = 0,f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = x*10+ch-'0'; ch = getchar();}
    return x*f;
}

int n,m;
int head[N],nxt[N*2],to[N*2],cnt;
int f[20][N],deep[N],ans[N],val[N],tong[N],Maxdeep,w[N],num[N*3];
struct node{
    int s,t,lca,len;
}a[N];
vector<int> scarlet[N],scarlet1[N],scarlet2[N];

inline void insert(int u,int v){
    to[++cnt] = v; nxt[cnt] = head[u]; head[u] = cnt;
    to[++cnt] = u; nxt[cnt] = head[v]; head[v] = cnt;
}

inline void dfs_first(int x,int fa){
    for(int i = head[x];i;i = nxt[i])
        if(to[i] != fa){
            deep[to[i]] = deep[x]+1;
            f[0][to[i]] = x;
            dfs_first(to[i],x);
        }
}

inline void plan(){
    for(int i = 1;i <= n;i++)
        Maxdeep = max(Maxdeep,deep[i]);
    for(int j = 1;j <= 19;j++)
        for(int i = 1;i <= n;i++)
            f[j][i] = f[j-1][f[j-1][i]];
}

inline int lca(int u,int v){
    if(deep[u] > deep[v]) swap(u,v);
    int dep = deep[v] - deep[u];
    for(int i = 19;i >= 0;i--)
        if(dep & (1 << i))
            v = f[i][v];
    if(u == v) return v;
    for(int i = 19;i >= 0;i--)
        if(f[i][u] != f[i][v]){
            u = f[i][u];
            v = f[i][v];
        }
    u = f[0][u];
    return u;
}

inline void dfs(int x,int fa){
    int now = w[x]+deep[x],cun = 0;
    if(now <= Maxdeep) cun = tong[now];
    for(int i = head[x];i;i = nxt[i])
        if(to[i] != fa)
            dfs(to[i],x);
    tong[deep[x]] += val[x];
    if(now <= Maxdeep) ans[x] = tong[now] - cun;
    for(int i = 0,l = scarlet[x].size();i < l;i++)
        tong[deep[scarlet[x][i]]]--;
        //因为之后是往上回溯,之后就走不到观察点了,就将此点为终点的路径起点当时所加的都减去。
        //因为我们都是直接用这个深度的值,所以也要减原先深度的值。
}

inline void DFS(int x,int fa){
    int now = deep[x] - w[x],cun;
    now += 300000; cun = num[now];
    for(int i = head[x];i;i = nxt[i])
        if(to[i] != fa)
            DFS(to[i],x);
    for(int i = 0,l = scarlet1[x].size();i < l;i++)
        num[300000+scarlet1[x][i]]++;
    ans[x] += num[now]-cun;
    for(int i = 0,l = scarlet2[x].size();i < l;i++)
        num[300000+scarlet2[x][i]]--;
}

int main(){
    n = read(); m = read();
    for(int i = 1;i < n;i++){
        int x = read(),y = read();
        insert(x,y);
    }
    for(int i = 1;i <= n;i++) w[i] = read();
    deep[1] = 1;
    f[0][1] = 1;
    dfs_first(1,0);
    plan();
    for(int i = 1;i <= m;i++){
        a[i].s = read(); a[i].t = read(); val[a[i].s]++;
        a[i].lca = lca(a[i].s,a[i].t);
        a[i].len = deep[a[i].s] + deep[a[i].t] - 2 * deep[a[i].lca];    //路径长度
        scarlet[a[i].lca].push_back(a[i].s);        //存下以a[i].lca为终点的路径起点有哪些
    }
    dfs(1,0);
    for(int i = 1;i <= m;i++){
        scarlet1[a[i].t].push_back(deep[a[i].t] - a[i].len);        //其实是和上面一样的,基本不用解释了
        scarlet2[a[i].lca].push_back(deep[a[i].t] - a[i].len);
    }
    DFS(1,0);
    for(int i = 1;i <= m;i++)
        if(deep[a[i].s]-deep[a[i].lca] == w[a[i].lca])
            ans[a[i].lca]--;
    for(int i = 1;i <= n;i++)
        printf("%d ",ans[i]);
    return 0;
}
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值