zoukankan      html  css  js  c++  java
  • 计蒜客 31451

    题目链接:https://nanti.jisuanke.com/t/31451

    Given a rooted tree ( the root is node $1$ ) of $N$ nodes. Initially, each node has zero point.

    Then, you need to handle $Q$ operations. There're two types:

    1 L X: Increase points by $X$ of all nodes whose depth equals $L$ ( the depth of the root is zero ). ($x leq 10^8$)

    2 X: Output sum of all points in the subtree whose root is $X$.

    Input
    Just one case.

    The first lines contain two integer, $N,Q$. ($N leq 10^5, Q leq 10^5$).

    The next $n-1$ lines: Each line has two integer $a,b$, means that node aa is the father of node $b$. It's guaranteed that the input data forms a rooted tree and node $1$ is the root of it.

    The next $Q$ lines are queries.

    Output
    For each query $2$, you should output a number means answer.

    题意:

    给出一棵 $N$ 个节点的树,初始每个节点的权值均为 $0$,接下来给出 $Q$ 个操作,有两种操作:

    第一种,修改操作,对所有深度为 $L$ 的节点(根节点的深度为 $0$),权值加上 $X$;

    第二种,查询操作,查询以节点 $X$ 为根节点的子树(包含节点 $X$)内所有节点的权值和。

    题解:

    说实话,比赛的时候,想到了DFS序+线段树的操作,但是不会搞时间复杂度卡死。然而,要知道,神仙的时间复杂度和蒟蒻的时间复杂度是不一样的。

    以下根据官方题解:

    设定一个层内节点数的阈值 $T$,

    当第 $L$ 层的节点数 $size_L < T$ 时,用DFS序把树拍平,

      修改操作,可以直接暴力枚举所有节点进行 $Oleft( {log N} ight)$ 修改,时间复杂度为 $Oleft( {Tlog N} ight)$;

      查询操作,直接就是 $Oleft( {log N} ight)$ 区间查询。

    因此,两种操作合起来的时间复杂度 $Oleft( {Q cdot Tlog N} ight)$。

    当第 $L$ 层的节点数 $size_L ge T$ 时,

      修改操作,存储每一层节点的权值(因为同一层内节点的权值永远是一样的),直接 $Oleft( 1 ight)$ 修改;

      查询操作,直接暴力枚举层,对于每一层,使用二分查找,得到属于“根为$X$的子树”的节点的个数,时间复杂度 $Oleft( {frac{N}{T}log N} ight)$。

    因此,两种操作合起来的时间复杂度 $Oleft( {Q cdot frac{N}{T}log N} ight)$。

    因此总的时间复杂度为 $Oleft( {Qlog Nleft( {T + frac{N}{T}} ight)} ight)$,所以令 $T = sqrt N$ 最优,时间复杂度为 $Oleft( {Qsqrt N log N} ight)$。

    AC代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=1e5+10;
    
    int n,q;
    int T;
    ll val_deep[maxn]; //存储深度为deep的节点的权值
    vector<int> Node_deep[maxn]; //存储深度为deep的节点的编号
    vector<int> bigL; //存储节点数不小于阈值的层编号
    
    //邻接表-st
    struct Edge{
        int u,v;
        Edge(int u=0,int v=0){this->u=u,this->v=v;}
    };
    vector<Edge> E;
    vector<int> G[maxn];
    void addedge(int u,int v)
    {
        E.push_back(Edge(u,v));
        G[u].push_back(E.size()-1);
    }
    //邻接表-ed
    
    //dfs序-st
    int dfs_clock=0;
    int in[maxn],out[maxn];
    void dfs(int now,int dep)
    {
        in[now]=++dfs_clock;
        Node_deep[dep].push_back(in[now]);
        for(int i=0;i<G[now].size();i++)
        {
            int nxt=E[G[now][i]].v;
            dfs(nxt,dep+1);
        }
        out[now]=dfs_clock;
    }
    //dfs序-ed
    
    struct _BIT{
        int N;
        ll C[maxn];
        int lowbit(int x){return x&(-x);}
        void init(int n) //初始化共有n个点
        {
            N=n;
            for(int i=1;i<=N;i++) C[i]=0;
        }
        void add(int pos,ll val) //在pos点加上val
        {
            while(pos<=N)
            {
                C[pos]+=val;
                pos+=lowbit(pos);
            }
        }
        ll sum(int pos) //查询1~pos点的和
        {
            ll ret=0;
            while(pos>0)
            {
                ret+=C[pos];
                pos-=lowbit(pos);
            }
            return ret;
        }
    }BIT;
    
    int main()
    {
        cin>>n>>q;
        T=((n>1000)?(int)sqrt(n):n);
        for(int i=1;i<n;i++)
        {
            int par,son; scanf("%d%d",&par,&son);
            addedge(par,son);
        }
    
        dfs(1,0);
    
        for(int dep=0;dep<=n;dep++)
        {
            if(Node_deep[dep].size()>=T) bigL.push_back(dep);
        }
    
    
        BIT.init(n);
        memset(val_deep,0,sizeof(val_deep));
        for(int i=1;i<=q;i++)
        {
            int type; scanf("%d",&type);
            if(type==1)
            {
                int L,X; scanf("%d%d",&L,&X);
                if(Node_deep[L].size()<T)
                    for(int k=0;k<Node_deep[L].size();k++) BIT.add(Node_deep[L][k],(ll)X);
                else
                    val_deep[L]+=X;
            }
            else
            {
                int X; scanf("%d",&X);
                ll ans=BIT.sum(out[X])-BIT.sum(in[X]-1);
                for(int k=0;k<bigL.size();k++)
                {
                    int dep=bigL[k];
                    int L=lower_bound(Node_deep[dep].begin(),Node_deep[dep].end(),in[X])-Node_deep[dep].begin();
                    int R=upper_bound(Node_deep[dep].begin(),Node_deep[dep].end(),out[X])-Node_deep[dep].begin();
                    ans+=(R-L)*val_deep[dep];
                }
                printf("%lld
    ",ans);
            }
        }
    }

    评测结果:

    可以看到,名字叫卡常的题目是最不卡常的。

  • 相关阅读:
    高位前缀和,求他的子集的和https://ac.nowcoder.com/acm/contest/4784/A
    Codeforces Global Round 7 E. Bombs
    高精度,乘法加法
    2018-ICPC-焦作区预赛
    状压dp,区间dp,矩阵快速幂
    树状数组,适用于单点修改,区间查询
    离散化函数
    带修莫队模版
    树链剖分 https://www.luogu.com.cn/problem/P3384
    HDU 1016 Prime Ring Problem【DFS】
  • 原文地址:https://www.cnblogs.com/dilthey/p/9612632.html
Copyright © 2011-2022 走看看