zoukankan      html  css  js  c++  java
  • [BZOJ3631][JLOI2014]松鼠的新家

    题目描述 Description

    松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n个房间,并且有n-1根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的。天哪,他居然真的住在“树”上。松鼠想邀请小熊维尼前来参观,并且还指定一份参观指南,他希望维尼能够按照他的指南顺序,先去a1,再去a2,……,最后到an,去参观新家。
    可是这样会导致维尼重复走很多房间,懒惰的维尼不听地推辞。可是松鼠告诉他,每走到一个房间,他就可以从房间拿一块糖果吃。维尼是个馋家伙,立马就答应了。
    现在松鼠希望知道为了保证维尼有糖果吃,他需要在每一个房间各放至少多少个糖果。因为松鼠参观指南上的最后一个房间an是餐厅,餐厅里他准备了丰盛的大餐,所以当维尼在参观的最后到达餐厅时就不需要再拿糖果吃了。

    输入描述 Input Description

    第一行一个整数n,表示房间个数
    第二行n个整数,依次描述a1-an
    接下来n-1行,每行两个整数x,y,表示标号x和y的两个房间之间有树枝相连。

    输出描述 Output Description

    一共n行,第i行输出标号为i的房间至少需要放多少个糖果,才能让维尼有糖果吃。 

    样例输入 Sample Input

    5
    1 4 5 3 2
    1 2
    2 4
    2 3
    4 5

    样例输出 Sample Output

    1
    2
    1
    2
    1

    数据范围及提示 Data Size & Hint

     2<= n <=300000

    之前的一些废话:会考结束,没出成绩,准备期末复习。说实话这道题看完之后发慌,因为。。有点像某道给我心理阴影面积极大的题(NOIP2016天天爱跑步

    题解:在树的路径上进行修改,其实这道题是可以那树链剖分+线段树水过的。但是我们对于这种离线的,可以使用一种叫做树上差分的技巧。还记得借教室那道题么?也是涉及到线性的区间修改,但是我们利用差分+前缀和的思想,不需要修改整个区间,只需要动区间两端即可,最后前缀和扫一遍就能把所有区间打的标记算好。树上的话也是类似,把a,b这段路径和都加上1的方法:在a,b位置各加上1,然后自底向上过程中就能把a,b路径上的点全部算入,然后汇聚到lca(a,b)的时候会算两次,所以我们需要在lca(a,b)处减1,之后在lca(a,b)以上的部分就不会进行修改了,所以我们再在fa[lca(a,b)]处减一即可。自底向上的话,用类似树形DP的方法来求即可。至于lca的部分,以后不玩倍增了,还是写树链剖分比较稳。

    代码:

    #include<iostream>
    #include<cmath>
    #include<algorithm>
    #include<cstring>
    #include<queue>
    #include<cstdio>
    using namespace std;
    typedef long long LL;
    #define mem(a,b) memset(a,b,sizeof(a))
    typedef pair<int,int> PII;
    inline int read()
    {
        int x=0,f=1;char c=getchar();
        while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
        while(isdigit(c)){x=x*10+c-'0';c=getchar();}
        return x*f;
    }
    const int maxn=300010;
    struct Edge
    {
        int u,v,next;
        Edge() {}
        Edge(int _1,int _2,int _3):u(_1),v(_2),next(_3) {}
    }e[maxn<<1];
    int n,ce=-1,es,a,b,A[maxn],first[maxn],deep[maxn],fa[maxn],size[maxn],ms[maxn],top[maxn],tag[maxn];
    void addEdge(int a,int b)
    {
        e[++ce]=Edge(a,b,first[a]);first[a]=ce;
        e[++ce]=Edge(b,a,first[b]);first[b]=ce;
    }
    void dfs(int now,int pa)
    {
        size[now]=1;
        for(int i=first[now];i!=-1;i=e[i].next)
            if(e[i].v!=pa)
            {
                fa[e[i].v]=now;deep[e[i].v]=deep[now]+1;
                dfs(e[i].v,now);
                size[now]+=size[e[i].v];
                if(size[e[i].v]>size[ms[now]])ms[now]=e[i].v;
            }
    }
    void divide(int now,int chain)
    {
        top[now]=chain;
        if(ms[now])divide(ms[now],chain);
        for(int i=first[now];i!=-1;i=e[i].next)
            if(e[i].v!=ms[now] && fa[now]!=e[i].v)divide(e[i].v,e[i].v);
    }
    int lca(int a,int b)
    {
        while(top[a]!=top[b])
        {
            if(deep[top[a]]<deep[top[b]])swap(a,b);
            a=fa[top[a]];
        }
        return deep[a]<deep[b] ? a : b;
    }
    void pushup(int now)
    {
        for(int i=first[now];i!=-1;i=e[i].next)
            if(fa[now]!=e[i].v)pushup(e[i].v),tag[now]+=tag[e[i].v];
    }
    int main()
    {
        mem(first,-1);
        n=read();
        for(int i=1;i<=n;i++)A[i]=read();
        for(int i=1;i<n;i++)a=read(),b=read(),addEdge(a,b);
        dfs(1,0);divide(1,1);
        for(int i=1;i<n;i++)
        {
            int t=lca(A[i],A[i+1]);
            tag[A[i]]++;tag[A[i+1]]++;
            tag[t]--;tag[fa[t]]--;
        }
        pushup(1);
        for(int i=2;i<=n;i++)tag[A[i]]--;
        for(int i=1;i<=n;i++)printf("%d
    ",tag[i]);
        return 0;
    }
    View Code

    总结:树上差分似乎近几年很爱考,一定要牢记。

  • 相关阅读:
    windows phone 7 开发工具合集
    Windows Phone中文开发资源集中营
    Windows Phone 7 输入法升起时,保持页面不被推起
    winXP控制面板TTS语音打不开卡顿SDK开发无法播放中文解决方法MFC调用代码
    c#winform不要通过文件右键属性去复制文件路径,会复制到隐藏的字符,打印路径会看见问号,导致无法打开指定文件
    密钥读yao还是yue?
    office access accdb驱动目录注册表路径
    c#winform判断是否为数字型字符串
    c#序列化json文件为字符串更改json对象内容
    在C#HttpWebRequest 设置超时方法
  • 原文地址:https://www.cnblogs.com/FYH-SSGSS/p/7078408.html
Copyright © 2011-2022 走看看