【NOIP2016】天天爱跑步之树上差分

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

题目

难道还有谁不知道的么

个人恩仇录

这是我第一次正经参加noip,然后遇到了这道题,记得当时好像连树都不会,还是很执着的搞掉了前4个点,然后还很用心的做了链那一个点并且自信过了七个点,现在回想一下当年的自信是哪里来的,原来是不会分析时间复杂度。。。
不过还是过了4个点,最后听说这是16年最难的题目就很开心啊,还有好多爆0的,我们省只有一个满分。
所以后来对这道题心存畏惧,然后第一次接触LCA和差分的时候还自信慢慢的来重新看了看题,结果还是不会。。。
最后是接近NOIP2017了,差分考得又比较多,所以感觉有必要研究一下,看了一个晚上的题解写了两个晚上才写出来,其实想一想这道题挺简单的,就是有细节上的失误和对这道题的畏惧之情。
反正最后搞出来了还是特别开心嘛。

分析

反正这是一个差分嘛,因为如果模拟的话时间复杂度是O(n*m*log2n),那就只有往差分的思路上想了。
注意:这里的差分指的尽量对于一个路径只标记几个点
首先抓住题目的一个要点——一个点之后固定起点的几个位置才有可能影响到这个点,如果强行统计的话时间复杂度还是太高,并且不知道时候影响到了结点(可能没走到就完了),还是要差分的。
我们可以很自然的或者说看看样例的把它拆成两条路,一个是上行,一个是下行。
我们来推一下满足什么要求才是影响结点的起点位置:
上行:dep[i]+w[i]==dep[u](很自然的移项,只和i有关)
下行:dep[i]-w[i]==2*dep[z]-dep[u] (z是路径两点的LCA)
所以我们在u标记上行,z删除,v标记下行,fa[z]删除(避免重复计算)
每次答案的统计就是子树中满足dep[i]+-w[i]的数量。
因为dep[i]+-w[i]都比较小,所以可以设置两个桶,为当前的某个值的数量
因为是统计子树,所以要设置一个变量为刚刚到达结点i的时候对应标记的值,最后来减去才是在子树中增加的值。

说明:为什么要分成两段路径?
因为常规的差分并没有时间这个要求,所以我们大概可以看成从两边同时往LCA走,但是这里要求了时间,如果拆成两段路径的话比较符合题意,这也是真实的走法。

code

//我就是不用vector 
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int oo=18,maxn=300000+10;
#define cntb(i) cntb[i+maxn]
#define cnta(i) cnta[i]

void Rd(int &x)
{
    x=0;
    char c;
    while((c=getchar())!=EOF && c<'0');
    do{
        x=(x<<3)+(x<<1)+(c^48);
    }while((c=getchar())!=EOF && c>='0');
}

int np,first[maxn];
struct edge{
    int to,next;
}E[maxn<<1];
void add(int u,int v)
{
    E[++np]=(edge){v,first[u]};
    first[u]=np;
}

int n,m,w[maxn];
int dfn[maxn],dep[maxn],fa[maxn][20];
int dfs_clock;
int cnta[maxn<<1],cntb[maxn<<1];
int ans[maxn];

struct data{
    int m,id;
    friend bool operator<(data a,data b)
    {
        return dfn[a.id]<dfn[b.id];
    }
}B[maxn],B0[maxn],A0[maxn],A[maxn];
int subb=1,subb0=1,suba0=1,suba=1;
void Init()
{
    int u,v;
    Rd(n);Rd(m);
    for(int i=1;i<n;i++)
    {
        Rd(u);
        Rd(v);
        add(u,v);
        add(v,u);
    }
    for(int i=1;i<=n;i++)
        Rd(w[i]);
    dfn[0]=maxn+5;
}

void DFS(int i,int f,int d)
{
    dfn[i]=++dfs_clock;
    dep[i]=d;
    fa[i][0]=f;
    for(int j=1;j<=oo;j++)
        fa[i][j]=fa[fa[i][j-1]][j-1];
    for(int p=first[i];p;p=E[p].next)
    {
        int j=E[p].to;
        if(j==f)continue;
        DFS(j,i,d+1);
    }
}

int LCA(int u,int v)
{
    if(dep[u]<dep[v])swap(u,v);
    int delt=dep[u]-dep[v];
    for(int j=0;j<=oo;j++)
        if(delt&(1<<j))
            u=fa[u][j];
    if(u==v)return u;
    for(int j=oo;j>=0;j--)
        if(fa[u][j]!=fa[v][j])
            u=fa[u][j],v=fa[v][j];
    return fa[u][0];
}

void update()
{
    int u,v,z;
    for(int i=1;i<=m;i++)
    {
        Rd(u);
        Rd(v);
        z=LCA(u,v);
        A[i]=(data){dep[u],u};
        A0[i]=(data){dep[u],fa[z][0]};
        B[i]=(data){2*dep[z]-dep[u],v};
        B0[i]=(data){2*dep[z]-dep[u],z};
    }

    sort(A+1,A+m+1);
    sort(B+1,B+m+1);
    sort(B0+1,B0+m+1);
    sort(A0+1,A0+m+1);
}
void DFS(int i,int f)
{   

    int now1=cnta(dep[i]+w[i]);
    int now2=cntb(dep[i]-w[i]);

    while(suba<=m&&A[suba].id==i)cnta(A[suba++].m)++;
    while(subb<=m&&B[subb].id==i)cntb(B[subb++].m)++;

    while(suba0<=m&&A0[suba0].id==i)cnta(A0[suba0++].m)--;
    while(subb0<=m&&B0[subb0].id==i)cntb(B0[subb0++].m)--;

    for(int p=first[i];p;p=E[p].next)
    {
        int j=E[p].to;
        if(j==f)continue;
        DFS(j,i);
    }

    ans[i]= cnta(dep[i]+w[i]) + cntb(dep[i]-w[i]) - now1 - now2;
}
int main()
{
    freopen("in.txt","r",stdin);
    freopen("out1.t","w",stdout);
    Init();
    DFS(1,0,0);
    update();
    DFS(1,0);
    for(int i=1;i<=n;i++)
        printf("%d ",ans[i]);
    return 0;
}    

注意
1、dep[i]-w[i]要加偏移变量
2、为了避免本校题库对STL的抗拒,特地想了一个办法来避免使用vector:使用一个数组记录下要修改的值,按照便利顺序排序(写一个dfs_clock),但是要注意的问题就是dfn[0]要赋值为inf,或者在弹出元素的时候写一个判断把0弹出来,反正确实要小心

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值