NOIP 2016 天天爱跑步

2 篇文章 0 订阅
1 篇文章 0 订阅

天天爱跑步

这道题。有点丧心病狂。。为什么会出现在NOIP里呢。。。先讲部分分:
对于前25分 瞎暴力即可。
对于Si=1的20分:
我们发现Si=1时,对于在点i的观察员。
深度为dep[i],出现时间为w[i]。因为所有的点都是由上到下的,所以只有S在上面时才会对点i贡献。
会对i造成贡献的点S: dep[i]-w[i]=dep[S]。而所有的点S都为1。
得到恒等式:dep[i]-w[i]=1。这样的点答案才不会为0.
所以可以树剖+线段树维护一下每个点经过的次数,然后对于dep[i]-w[i]!=1输出0,否则输出线段树上 那个点的值。

对于Ti=1的20分:
所有的点都向上走。我们思考点怎么获得贡献。
向上走 点i若获得贡献:dep[i]+w[i]=dep[s]。起点S的dep满足这个条件到那个点即可有贡献。
所以开一个数组记录从X出发的点现在还没结束的有多少。(桶的思想)。
记录下以x为起点的有多少个,dfs一遍,当这个起点退栈时说明现在这个路径在dfs出栈的过程了。而终点都是1,所以不会出栈,就回溯时统计下子树中点到这里的个数就好了。
那么如何统计子树呢?根据dfs序。到达时记录tmp,回溯时的值相减就是子树的贡献。

对于一条链的15分:
线性的一个桶,从左往右扫。vector存关系。
记录下当前的起点有多少条线段。线性扫的时候,每个点进行三个操作存入桶中,计算,出桶。
从右向左同理。

对于100%的得分:
我们知道一条路上树的路径有且只有一条。
综上所述:我们把路径S->T拆成从S->LCA(S,T)和LCA(S,T)->T。
一条向上的路径和一条向下的路径。

我们对于每个观测点进行统计。
对于向上的路径非常简单,一个点i贡献当且仅当dep【i】+w【i】=dep【s】时才有贡献。
记录每个点出发的路径条数,和用vector记录到哪个点以s开始的路径中断。
dfs时注意判断越界的情况不合法,不能计算(重要)。
到一个点的时候同样先记录tmp——dep深度的起点有多少个。
回溯时更新该深度起点的值和该点的答案。注意是深度!最后该点出栈时,把桶里的东西拿出来(深度!)。

从上往下就有点麻烦。。当LCA是S时。dep【i】-w【i】=dep【s】时有贡献,然而。如果起点是拐弯过来的呢?发现dep【i】-w【i】的值得到的是负的拐弯深度。有点难思考,w【i】的实质其实就是他们的距离。所以要找到起点,我们可以考虑这是一个边权为1的树。dis(s,t)不就是 s到lca和lca到t吗。
dep【i】-w【i】如果是起点层的深度,那么一定有dep【t】-dis(s,t)即负的s到lca=dep【i】-w【i】。

所以我们记录lca和t的起点层深度都是dep【t】-dis(s,t)。
终点下面的点肯定没有影响,LCA之上的点也没有影响。让LCA到终点之间的点统计这段距离,那么就在终点出栈时放入桶中,起点出栈时拿出桶中。
注意设置一个点数MAXN的偏移量防止负数。

#include<bits/stdc++.h>
using namespace std;

const int MAXN=3e5+5;

struct edge{
    int to,next,w;
}e[MAXN<<1];

struct data{
    int u,v,lca,dis;
}nd[MAXN];

int n,m,w[MAXN];

int head[MAXN],cnt=0;
inline void add(int u,int v){
    e[++cnt]=(edge){v,head[u],1},head[u]=cnt;
    e[++cnt]=(edge){u,head[v],1},head[v]=cnt; 
}

int fa[MAXN],dep[MAXN],size[MAXN],hson[MAXN],dis[MAXN],DEP=0;
void dfs1(int u,int father){
    size[u]=1;
    dep[u]=dep[father]+1;
    fa[u]=father;
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to,w=e[i].w;
        if(v==father)continue;
        dis[v]=dis[u]+w;
        dfs1(v,u);
        size[u]+=size[v];
        if(size[v]>size[hson[u]]||!hson[u])hson[u]=v;
    }
}

int top[MAXN];
void dfs2(int u,int tp){
    top[u]=tp;
    if(hson[u])dfs2(hson[u],tp);
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        if(v==fa[u]||v==hson[u])continue;
        dfs2(v,v);
    }
}

inline int lca(int x,int y){
    int tx=top[x],ty=top[y];
    while(tx!=ty){
        if(dep[tx]>dep[ty])
            x=fa[tx];
        else 
            y=fa[ty];
        tx=top[x],ty=top[y];
    }
    if(dep[x]<dep[y])swap(x,y);
    return y; 
}

vector<int>v1[MAXN],v2[MAXN<<1],v3[MAXN<<1];
int val[MAXN];

int ans[MAXN],A[MAXN],B[MAXN<<1];

//往上走的部分。 
void getans1(int u,int fa){ 
    int tmp;
    if(dep[u]+w[u]<=DEP)tmp=A[dep[u]+w[u]];//记录下面到他的点现在是多少 
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        if(v==fa)continue;
        getans1(v,u);
    }   
    A[dep[u]]+=val[u];//给这一层加上路径 入栈 
    if(dep[u]+w[u]<=DEP)ans[u]+=A[dep[u]+w[u]]-tmp;//子树里的答案 退栈时子树一定被遍历了 
    for(int i=0;i<v1[u].size();i++){
        A[dep[v1[u][i]]]--;//出栈时 到这里结束的点(那个深度)退掉 避免影响父亲中还在栈里的点 
    } 
}

//往下走的部分   我们只关心这个点i 有没有从他对应的起点可以到达i的点。
//所以记录下起点 终点来入栈出栈。 
//注意,getans1中因为是从下到上,所以是起点在下面 所以是起点入栈开始记录 
//同样记录差值。 
void getans2(int u,int fa){
    int tmp=B[dep[u]-w[u]+300000];
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        if(v==fa)continue;
        getans2(v,u);
    }   
    for(int i=0;i<v3[u].size();i++){//终点出栈 记录 
        B[v3[u][i]+300000]++;//本身这就是一层里的起点了 不用dep 
    }
    ans[u]+=B[dep[u]-w[u]+300000]-tmp;
    for(int i=0;i<v2[u].size();i++){//起点出栈 删掉 
        B[v2[u][i]+300000]--;
    }
}

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);
    }
    for(int i=1;i<=n;i++){
        scanf("%d",&w[i]);
    }   
    dfs1(1,1);
    dfs2(1,1);
    for(int i=1;i<=n;i++)DEP=max(dep[i],DEP); 
    for(int i=1;i<=m;i++){
        scanf("%d%d",&nd[i].u,&nd[i].v);
        nd[i].lca=lca(nd[i].u,nd[i].v);
        nd[i].dis=dep[nd[i].u]+dep[nd[i].v]-(dep[nd[i].lca]<<1);
        v1[nd[i].lca].push_back(nd[i].u);//记录向上路径出栈顺序
        val[nd[i].u]++;//记录向上路径的条数 
    }
    getans1(1,0);
    for(int i=1;i<=m;i++){
        v2[nd[i].lca].push_back(dep[nd[i].v]-nd[i].dis);//记录起点 
        v3[nd[i].v].push_back(dep[nd[i].v]-nd[i].dis);//记录终点
        //当且仅当 dep[i]-w[i]=起点(dep[t]-dis(s,t)
    }
    getans2(1,0);
    for(int i=1;i<=m;i++){//向上 dep[i]+w[i]=dep[s] 
        if(dep[nd[i].lca]+w[nd[i].lca]==dep[nd[i].u])ans[nd[i].lca]--;
    }
    for(int i=1;i<=n;i++)printf("%d ",ans[i]);
    return 0;
} 
/*
4 1
1 2
2 4 
1 3
2 1 0 1
4 1
*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值