NOIP 2016 Day1 T2 天天爱跑步

NOIP 2016 天天爱跑步

题面自己百度就有。


一棵树,树上跑路,数据范围大,显然看出是LCA。

根据LCA的性质,若一个人的起点为st,终点为en,这条路径必然经过LCA(st,en)。那么作出以下分析:

这里写图片描述

对于在st->en路径上的一点x,显然有两种情况:

CASE 1: x在st->LCA上
CASE 2: x在LCA->en上

下面讨论能够在x处观测到st处出发的人的条件,对于CASE 1,显然满足关系式dep[st]-dep[x]=w[x] , dep表示该点的深度,这在倍增LCA预处理时已经求出。观察移项后的式子dep[st]=dep[x]+w[x],那么我们只需要找出以x为根的子树中,dep满足条件的即可。

对于CASE 2要稍微麻烦一些。如果借鉴CASE 1的思路,容易写出关系式:dep[x]+dep[st]-2*dep[lca]=w[x],但是这样不好,因为有一个dep[lca]好像不好像CASE 1那样处理。解决办法是,设dis[en]表示st到en的路径长度,那么dis[en]=dep[st]+dep[en]-dep[lca]*2,原式化为dis[en]-(dep[en]-dep[x])=w[x],移项后转化为很好的形式:dis[en]-dep[en]=w[x]-dep[x],这样就可以像CASE 1那样处理了。

如果要在这样的数据范围下过题,容易想到一边DFS遍历整棵树,一边计算答案。首先考虑,如果数据范围没有这么大,我们会怎么办?显然应该会想到对每一个节点开一个cnt数组,记录它的子树中各种深度的节点的个数。这样是正确的,但是即使开动态数组也会MLE。那么考虑能不能只用一个cnt数组做到这件事。事实上正解就只对两种情况各用了一个数组。

考虑x子树中的节点y对Ans[x]的贡献。可以发现,对于在y号节点出发的人,如果起点与终点的lca也是x的子节点,那么对Ans[x]是不会有贡献的。这里得出了一个重要结论:对于人(st,en),它对lca以上的节点不会产生贡献。那么处理方法就是:对于节点x,首先讨论它的所有子树,在回溯时更新答案,并把自己对应的值在cnt数组中加上1,当讨论到lca时在cnt数组中减去1即可。为了记录节点是哪些人起点与终点的lca,开一个vector就可以了,看起来有300000个vector数组,但是总的元素个数实际上也只有300000,并不会MLE。

注意扩栈。


#include<stdio.h>
#include<vector>
#define MAXN 300005
#define MAXM 600005
#define add 300000
using namespace std;

int N,M,w[MAXN],v[MAXN],Ans[MAXN],dis[MAXN];
vector<int>ST[MAXN],EN[MAXN],G[MAXN];

int en[MAXM],las[MAXN],nex[MAXM],tot;
void ADD(int x,int y)
{
    en[++tot]=y;
    nex[tot]=las[x];
    las[x]=tot;
}

int dep[MAXN],fa[MAXN][21];
void DFS(int x,int f)
{
    int i,y;
    dep[x]=dep[f]+1;
    fa[x][0]=f;
    for(i=1;i<20;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
    for(i=las[x];i;i=nex[i])
    {
        y=en[i];
        if(y!=f)DFS(y,x);
    }
}

int LCA(int x,int y)
{
    int t,d,i;
    if(dep[x]<dep[y])t=x,x=y,y=t;
    d=dep[x]-dep[y];
    for(i=0;i<20;i++)if(d&(1<<i))x=fa[x][i];
    if(x==y)return x;
    for(i=20;i>=0;i--)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}

int cnt_1[MAXN*2];
void GetAns_1(int x,int f)
{
    int i,y,tmp=cnt_1[dep[x]+w[x]];//*2
    for(i=las[x];i;i=nex[i])
    {
        y=en[i];
        if(y!=f)GetAns_1(y,x);
    }
    cnt_1[dep[x]]+=v[x];
    Ans[x]+=cnt_1[dep[x]+w[x]]-tmp;
    for(i=0;i<ST[x].size();i++)cnt_1[dep[ST[x][i]]]--;
}

int cnt_2[MAXN*2];
void GetAns_2(int x,int f)
{
    int i,y,tmp=cnt_2[dep[x]-w[x]+add];//*2
    for(i=las[x];i;i=nex[i])
    {
        y=en[i];
        if(y!=f)GetAns_2(y,x);
    }
    for(i=0;i<G[x].size();i++)cnt_2[G[x][i]+add]++;
    Ans[x]+=cnt_2[dep[x]-w[x]+add]-tmp;
    for(i=0;i<EN[x].size();i++)cnt_2[EN[x][i]+add]--;
}

int main_main()
{
    int i,x,y,z;

    scanf("%d%d",&N,&M);
    for(i=1;i<N;i++)scanf("%d%d",&x,&y),ADD(x,y),ADD(y,x);
    for(i=1;i<=N;i++)scanf("%d",&w[i]);
    DFS(1,0);

    for(i=1;i<=M;i++)
    {
        scanf("%d%d",&x,&y);
        z=LCA(x,y);v[x]++;
        ST[z].push_back(x);

        dis[y]=dep[x]+dep[y]-2*dep[z];
        G[y].push_back(dep[y]-dis[y]);
        EN[z].push_back(dep[y]-dis[y]);

        if(dep[x]-dep[z]==w[z])Ans[z]--;//*1
    }

    GetAns_1(1,0);
    GetAns_2(1,0);

    for(i=1;i<=N;i++)printf("%d ",Ans[i]);
}
const int main_stack=16;    
char my_stack[128<<20];    
int main() {
  __asm__("movl %%esp, (%%eax);\n"::"a"(my_stack):"memory");    
  __asm__("movl %%eax, %%esp;\n"::"a"(my_stack+sizeof(my_stack)-main_stack):"%esp");    
  main_main();    
  __asm__("movl (%%eax), %%esp;\n"::"a"(my_stack):"%esp");    
  return 0;    
}

注意两个小细节:
1.如果恰好在lca(st,en)上能够观测到,那么这种情况会被讨论到两次,这时候注意去重。(代码中*1处)
2.对于x子树中的节点y,它的影响可能会达到x的上部,所以Ans的增量应该是深搜子树前后cnt的差值。(代码中*2处)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值