(银牌题)ACM-ICPC 2018 沈阳赛区网络预赛 J - Ka Chang dfs时间戳+树状数组+二分+分块

博客目录

原题

 

题目传送门

  •  18.55%
  •  1000ms
  •  131072K

Given a rooted tree ( the root is node 11 ) of NN nodes. Initially, each node has zero point.

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

1\ L\ X1 L X: Increase points by XX of all nodes whose depth equals LL ( the depth of the root is zero ). (x \leq 10^8)(x≤108)

2\ X2 X: Output sum of all points in the subtree whose root is XX.

Input

Just one case.

The first lines contain two integer, N,QN,Q. (N \leq 10^5, Q \leq 10^5)(N≤105,Q≤105).

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

The next QQ lines are queries.

Output

For each query 22, you should output a number means answer.

样例输入复制

3 3
1 2
2 3
1 1 1
2 1
2 3

样例输出复制

1
0

题目来源

ACM-ICPC 2018 沈阳赛区网络预赛

大意

给一个树,root为1,n个节点(不一定是二叉树)。一开始所有点权为0,然后给出两种操作:

1.给深度为L的所有节点点权加x

2.查询某个节点x作为根的子树所有点权和

n个节点q个操作,都是1e5

解析

这种看上去不能使用数据结构的东西就考虑分块(学到了)

对于操作1,很容易想到我们最好使用lazy标志,即入果一层上节点数很多的时候用一个标记记录下变化量而不是真正的修改所有的点。这就有了分块思想,如果这一层节点数很少,小于sqrt(n),则暴力修改,否则记录到lazy标记不真正修改。这样修改复杂度变为q*2*sqrt(n)

对于操作2,我们要知道节点x在每层上有多少个点。我们首先确定x所有点是什么。

我们对树遍历一遍,遍历时维护一个时间戳(计数),然后维护两个数组head和tail,head记录第一次到达当前点的时间,tail记录最后一次到达(离开)当前点的时间。这样head和tail之间的时间经过的点就是它的子树节点。这就相当于把子树求和变成了线性区间求和,将树状的节点号变为了线性的时间戳。然后用树状数组维护区间和。

使用dp[i]数组维护第i层所有点的时间戳。对于询问x,如果第i层节点数大于sqrt(n),我们只要在每一层dp[i]中二分两个时间戳:head和tail,用一个减法就能计算出在第i层中有多少个节点在x的子树中。然后节点数乘以当前层的lazy标记即可,而对于节点数小于sqrt(n),则刚刚已经暴力更新到树里去了,只要一次树状数组求和即可,然后两个加起来就是最终答案。

AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,q;
ll const maxn=1e5+10; 
vector<ll>mp[maxn];
ll co[maxn];
ll cnt=1;//时间戳 
ll head[maxn],tail[maxn];//第一次和最后一次   其中head也是从序号到时间戳的映射 
ll c[maxn];//tree
vector<ll>dp[maxn];//deep深度 
ll echo[maxn];
ll maxdp;
#define Lowbit(i) (i&-i)
inline void update(ll i, ll x) //i点增量为x
{
    while(i <= n)
    {
        c[i] += x;
        i += Lowbit(i);
    }
}
inline ll sum(ll x)//区间求和 [1,x]
{
    ll sum=0;
    while(x>0)
    {
        sum+=c[x];//从后面的节点往前加
        x-=Lowbit(x);//同层中向前移动一格,如果遇到L=1的节点会减成0
    }
    return sum;
}

inline ll Getsum(ll x1,ll x2) //求任意区间和[l,r]
{
    return sum(x2) - sum(x1-1);
}
void dfs(ll rt,ll deep){  //时间戳
    maxdp=max(maxdp,deep);
    echo[rt]=deep;
    head[rt]=cnt;
    dp[deep].push_back(cnt);
    cnt++;
    ll sz=mp[rt].size();
    ll cu;
    for(ll i=0;i<sz;i++){
        cu=mp[rt][i];
            dfs(cu,deep+1);
    }
    tail[rt]=cnt;
}
ll lz[maxn];//分块深度lazy标志 
signed main(){
    #ifndef ONLINE_JUDGE
    freopen("r.txt","r",stdin);
    #endif
    cin>>n>>q;  //只有查询的时候才才用原来的标号rt,计算时都用cnt(dfs序、时间戳来表示节点) 
    ll a,b;
    for(ll i=0;i<n-1;i++){
        scanf("%lld%lld",&a,&b);
        mp[a].push_back(b);
    //    mp[b].push_back(a);
    }
    dfs(1,0);
    ll op;
    ll bk=ceil(sqrt(n));//分块 
    ll l,r,x;
    ll sz,t;
    ll ans;
    vector<ll>::iterator it;
    vector<ll>big;
    for(ll i=0;i<maxdp;i++){
        if((ll)dp[i].size()>=bk)
            big.push_back(i);
    }
    while(q--){
        scanf("%lld",&op);
        if(op==1){
            scanf("%lld%lld",&l,&x);
            if((ll)dp[l].size()>=bk){
                lz[l]+=x;
            } 
            else{
                //暴力更新
                sz=dp[l].size();
                for(ll i=0;i<sz;i++){
                    update(dp[l][i],x);
                }
            }
        }else{
            //query
            scanf("%lld",&x);
            ans=Getsum(head[x],tail[x]-1);
            ll d=lower_bound(big.begin(),big.end(),echo[x])-big.begin();
            while(d<(ll)big.size() && big[d]<=maxdp){
                t=big[d++];
                l=lower_bound(dp[t].begin(),dp[t].end(),head[x])-dp[t].begin();
                r=lower_bound(dp[t].begin(),dp[t].end(),tail[x])-dp[t].begin()-1;
                ans+=(r-l+1)*lz[t];
            }
            printf("%lld\n",ans);
        }
    }
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值