1. 程式人生 > >(銀牌題)ACM-ICPC 2018 瀋陽賽區網路預賽 J

(銀牌題)ACM-ICPC 2018 瀋陽賽區網路預賽 J

部落格目錄

原題

題目傳送門

  •  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

題目來源

大意

給一個樹,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);
        }
    }
}