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

    Description

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

    Input

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

    Output

    一共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

    HINT

    2<= n <=300000

    【解析】

    上网上搜题解 说是一道裸题。。。然而我并没有看出哪里裸orz。(我很纯洁QWQ)

    昨晚徐老大给我讲了查分和前缀和。

    写一下....

    ===============================如果你知道差分就不用看下面我bb了。====================================================

    随便写一个序列 3 6 2 7 5 

    它的查分序列是用后一个数减去前一个数,第一个数什么都不减就是它本身。

    差分序列 3  3  -4  5  -2

    差分序列有一个性质,就是你从头往后累加,加到第几个位置,现在累加的数就是原序列这个位置的数。

    例如:差分序列加到第三个位置 3+3+(-4)=2;正好是原序列第三个位置的数。

    应用一下吧。。。。我们要对一个区间的子区间进行操作.

    例如[1,10];我们要将[4,6]这个区间的所有数加+1,求[4,6]的区间和。当然可以for循环挨个加1,求和;

    那么差分是怎样求呢。我们只需更改两个位置,假如我们进行修改的区间是[l,r]。将a[l]+1,a[r+1]-1;即可完成区间的更改。

    上面的例子 [4,6] .a[4]+1,a[6+1]-1;这样从头开始累加,由于我们在a[4]+1,我们从头累加到a[4-6]都会加+1;当我们累加到[4,6]

    这个区间以外,由于a[6+1]已经-1,所以之后的累加都-1,都是它原来数的大小。为什么累加就是利用的差分序列的性质。

    ==============================orz===分界线===zro==========================================================

    咳咳 进入正题:

    题目大意:就是求每个房间被访问的次数。将每条路径上的结点的权值+1,最后求每个结点的值。

    然而我还没有看出这是一道裸体。(哦不 裸题。=.=)

    ================================下面的结论挺好==============================================================

    我上网上搜了一下差分的性质,,我就是想看看怎么用差分。

    好:树上差分的结论:

     1、将每条路径(s,t)上的每个点权值增加1,求各点权值。

    将s、t点的权值加+1,lca(s,t)的权值-1,s和t的lca的爸爸-1。从叶子结点开始向上累加权值。

    恍然大悟WAW。这道题真是赤裸裸的一道裸题啊。至于为什么那个点+那个点-,结合差分序列的性质自己顿悟。

    2、找出被所有路径都覆盖的边

    s,t的权值+1,s和t的lca的权值-2,从叶结点向上累加。

    最终权值为路径数的点到其父亲的边为所求边。

    ========================================================================================================

    所以我们把他访问的路径看成一个区间,区间的结点的权值都+1,就用差分做好了。

    但是有一个坑,这一条路径的起点是这一条路径的终点,所以不能重复给糖,最后2--n减去1就可以。

    =========================================================================================================

    终于到贴代码的时间了。QWQ。

    【代码】

    #include<iostream>
    #include<cstdio>
    #include<vector>
    using namespace std;
    #define N 300009
    int n,x,y;
    int a[N],v[N],deep[N],dad[N][22];
    vector<int>vec[N];
    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 f*x;
    }
    inline void dfs(int x)
    {
        deep[x]=deep[dad[x][0]]+1;
        for(int i=0;dad[x][i];i++)
        dad[x][i+1]=dad[dad[x][i]][i];
        for(int i=0;i<vec[x].size();i++)
        {
            if(!deep[vec[x][i]])
            {
                dad[vec[x][i]][0]=x;
                dfs(vec[x][i]);
            }
        }
    }
    inline int lca(int x,int y)
    {
        if(deep[x]>deep[y])swap(x,y);
        for(int i=20;i>=0;i--){if(deep[dad[y][i]]>=deep[x])y=dad[y][i];}
        if(x==y)return x;
        for(int i=20;i>=0;i--)if(deep[dad[x][i]]!=deep[dad[y][i]])x=dad[x][i],y=dad[y][i];
        return dad[x][0];
    }
    inline void dfs1(int x)
    {
        for(int i=0;i<vec[x].size();i++)
        {
            if(dad[x][0]!=vec[x][i])
            dfs1(vec[x][i]),v[x]+=v[vec[x][i]];//从叶子结点向上累加权值 
        }
    }
    int main()
    {
         n=read();
         for(int i=1;i<=n;i++)
         a[i]=read();
         for(int i=1;i<n;i++)
         {
             x=read();y=read();
             vec[x].push_back(y);
             vec[y].push_back(x);
         }
         dfs(1);
         for(int i=1;i<n;i++)//差分 
         {
             v[a[i]]++;v[a[i+1]]++;
             v[lca(a[i],a[i+1])]--;
             v[dad[lca(a[i],a[i+1])][0]]--;
         }
         dfs1(1);
         for(int i=2;i<=n;i++)
         v[a[i]]--;//减去重复的。 
         for(int i=1;i<=n;i++)
         {
             printf("%d
    ",v[i]);
         }
         return 0;
    } 

     然而上一个代码是超时的orz。

    因为树剖的复杂度是logn,tarjan n+q,理论上是tarjan最优

    因为tarjan要跑完整个图,所以他的实际复杂度比理论复杂度高。而树剖可能跑不完整个图就找到答案,这时树剖就比tarjan优秀了。

    如果要写tarjan,写上路径压缩和启发式合并(然而我并不知道启发式合并是什么鬼)。

    下面是树剖的code

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<vector>
    #define MAXN 300000+15
    using namespace std;
    int n,x,y,ans[MAXN],a[MAXN];
    vector<int>vec[MAXN];
    int size[MAXN],dad[MAXN],deep[MAXN],top[MAXN];
    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 f*x;
    }
    inline void dfs(int x){
        deep[x]=deep[dad[x]]+1;
        size[x]=1;
        for(int i=0;i<vec[x].size();i++)
            if(vec[x][i]!=dad[x]){
                dad[vec[x][i]]=x;
                dfs(vec[x][i]);
                size[x]+=size[vec[x][i]];
            }
    }
    inline void dfs1(int x){
        int t=0;
        if(!top[x])    top[x]=x;
        for(int i=0;i<vec[x].size();i++)
            if(dad[x]!=vec[x][i]&&size[vec[x][i]]>size[t])
                t=vec[x][i];
        if(t){
            top[t]=top[x];
            dfs1(t);
        }    
        for(int i=0;i<vec[x].size();i++)
            if(dad[x]!=vec[x][i]&&vec[x][i]!=t)
                dfs1(vec[x][i]);
    }
    inline int LCA(int x,int y){
        for(;top[x]!=top[y];){
            if(deep[top[x]]<deep[top[y]])
                swap(x,y);
            x=dad[top[x]];
        }
        if(deep[x]>deep[y])
            swap(x,y);
        return x;
    }
    inline void dfs2(int x){
        for(int i=0;i<vec[x].size();i++)
            if(dad[x]!=vec[x][i]){
                dfs2(vec[x][i]);
                ans[x]+=ans[vec[x][i]];
            }
    }
    int main(){
        n=read();
        for(int i=1;i<=n;i++)
            a[i]=read();
        for(int i=1;i<n;i++){
            x=read();
            y=read();
            vec[x].push_back(y);
            vec[y].push_back(x);
        }
        deep[1]=1;
        dfs(1);
        dfs1(1);
        for(int i=2;i<=n;i++){
            int x=a[i-1],y=a[i],z=LCA(x,y);
            ans[x]++;ans[dad[y]]++;
            ans[z]--;ans[dad[z]]--;
        }
        dfs2(1);
        for(int i=1;i<=n;i++)    cout<<ans[i]<<endl;
    }
  • 相关阅读:
    Blob对象转File对象
    TCP的三次握手 与 四次挥手
    HTTP协议 与 TCP协议 的区别
    tcp四次挥手
    tcp协议
    tcp的三次握手
    关于HTTP协议 && TCP协议的一些理解
    Javascript数组中常用的排序法 ——冒泡排序法和选择排序法以及快速排序法
    如何用Bat批处理自制自解压文件
    [SQL]查询所有数据库、表名、表字段总结
  • 原文地址:https://www.cnblogs.com/zzyh/p/6816019.html
Copyright © 2011-2022 走看看