1. 程式人生 > >[Noip2016]天天愛跑步 樹上差分

[Noip2016]天天愛跑步 樹上差分

stact 0ms 情況下 類型 輸出 記錄 一行 push pri

$ \rightarrow $ 戳我進洛谷原題** $ \rightarrow $ 戳我進BZOJ原題**

天天愛跑步

 

時空限制 \quad 2000ms / 512MB

題目描述

小c同學認為跑步非常有趣,於是決定制作一款叫做《天天愛跑步》的遊戲。
《天天愛跑步》是一個養成類遊戲,需要玩家每天按時上線,完成打卡任務。
 
這個遊戲的地圖可以看作一一棵包含 $ n $ 個結點和 $ n?1 $ 條邊的樹, 每條邊連接兩個結點,且任意兩個結點存在一條路徑互相可達。
樹上結點編號為從 $ 1 $ 到 $ n $ 的連續正整數。
 
現在有 $ m $ 個玩家,第 $ i $ 個玩家的起點為 $ S_i $ ,終點為 $ T_i $ 。

每天打卡任務開始時,所有玩家在第 $ 0 $ 秒同時從自己的起點出發, 以每秒跑一條邊的速度,
不間斷地沿著最短路徑向著自己的終點跑去, 跑到終點後該玩家就算完成了打卡任務。 (由於地圖是一棵樹, 所以每個人的路徑是唯一的)
 
小c想知道遊戲的活躍度, 所以在每個結點上都放置了一個觀察員。
在結點 $ j $ 的觀察員會選擇在第 $ W_j $ 秒觀察玩家, 一個玩家能被這個觀察員觀察到當且僅當該玩家在第 $ W_j $ 秒也理到達了結點 $ j $ 。
小C想知道每個觀察員會觀察到多少人?
 
註意: 我們認為一個玩家到達自己的終點後該玩家就會結束遊戲, 他不能等待一 段時間後再被觀察員觀察到。
即對於把結點 $ j $ 作為終點的玩家: 若他在第 $ W_j $ 秒前到達終點,則在結點 $ j $ 的觀察員不能觀察到該玩家;
若他正好在第 $ W_j $ 秒到達終點,則在結點 $ j $ 的觀察員可以觀察到這個玩家。
 

輸入輸出格式

輸入格式

第一行有兩個整數 $ n $ 和 $ m $ 。其中 $ n $ 代表樹的結點數量, 同時也是觀察員的數量, $ m $ 代表玩家的數量。

接下來 $ n?1 $ 行每行兩個整數 $ u $ 和 $ v $ ,表示結點 $ u $ 到結點 $ v $ 有一條邊。

接下來一行 $ n $ 個整數,其中第 $ j $ 個整數為 $ W_j $ , 表示結點 $ j $ 出現觀察員的時間。

接下來 $ m $ 行,每行兩個整數 $ S_i $ ,和 $ T_i $ ,表示一個玩家的起點和終點。

對於所有的數據,保證 $ 1≤Si,Ti≤n,0≤Wj≤n1\leq S_i,T_i\leq n, 0\leq W_j\leq n $ 。

輸出格式

輸出1行 $ n $ 個整數,第 $ j $ 個整數表示結點 $ j $ 的觀察員可以觀察到多少人。
 

輸入輸出樣例

輸入樣例#1

 6 3
 2 3
 1 2 
 1 4 
 4 5 
 4 6 
 0 2 5 1 2 3 
 1 5 
 1 3 
 2 6 

輸出樣例#1

 2 0 0 1 1 1 

輸入樣例#2

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

輸出樣例#2

 1 2 1 0 1 

 

說明

【樣例1說明】

對於 $ 1 $ 號點,$ W_i=0 $ ,故只有起點為1號點的玩家才會被觀察到,
所以玩家 $ 1 $ 和玩家 $ 2 $ 被觀察到,共有 $ 2 $ 人被觀察到。

對於$ 2 $ 號點,沒有玩家在第 $ 2 $ 秒時在此結點,共 $ 0 $ 人被觀察到。

對於 $ 3 $ 號點,沒有玩家在第 $ 5 $ 秒時在此結點,共 $ 0 $ 人被觀察到。

對於 $ 4 $ 號點,玩家 $ 1 $ 被觀察到,共 $ 1 $ 人被觀察到。

對於 $ 5 $ 號點,玩家 $ 1 $ 被觀察到,共 $ 1 $ 人被觀察到。

對於 $ 6 $ 號點,玩家 $ 3 $ 被觀察到,共 $ 1 $ 人被觀察到。
 

【子任務】

每個測試點的數據規模及特點如下表所示。 提示: 數據範圍的個位上的數字可以幫助判斷是哪一種數據類型。

技術分享圖片

【提示】
如果你的程序需要用到較大的棧空問 (這通常意味著需要較深層數的遞歸),
請務必仔細閱讀選手日錄下的文本當rumung:/stact.p″,
以了解在最終評測時棧空問的限制與在當前工作環境下調整棧空問限制的方法。
 
在最終評測時,調用棧占用的空間大小不會有單獨的限制,但在我們的工作環境中默認會有 $ 8MB $ 的限制。
這可能會引起函數調用層數較多時, 程序發生棧溢出崩潰。
 
我們可以使用一些方法修改調用棧的大小限制。 例如, 在終端中輸入下列命令 ulimit -s 1048576
 
此命令的意義是,將調用棧的大小限制修改為 $ 1GB $
 
例如,在選手目錄建立如下 sample.cpp 或 sample.pas

技術分享圖片

將上述源代碼編譯為可執行文件 sample 後,可以在終端中運行如下命令運行該程序
 
./sample
 
如果在沒有使用命令“ ulimit -s 1048576”的情況下運行該程序, sample會因為棧溢出而崩潰;
如果使用了上述命令後運行該程序,該程序則不會崩潰。
 
特別地, 當你打開多個終端時, 它們並不會共享該命令, 你需要分別對它們運行該命令。
 
請註意, 調用棧占用的空間會計入總空間占用中, 和程序其他部分占用的內存共同受到內存限制。

 

題解

  • 列出恰好路過的條件並化簡

  • 在 $ S_i $ 到 $ lca(S_i,T_i) $ 的階段,應滿足 $ d[S_i]=w[x]+d[x] $

  • 在 $ lca(S_i,T_i) $ 到 $ T_i $ 階段,應滿足 $ d[S_i]-2*d[lca(S_i,T_i)]=w[x]-d[x] $

  • 相當於在 $ S_i $ 位置出現一個 $ A $ 類數 $ d[S_i] $ ,在 $ lca(S_i,T_i) $ 的父節點消失

  • 在 $ T_i $ 位置出現一個 $ B $ 類數 $ d[S_i]-2*d[lca(S_i,T_i)] $ ,在 $ lca(S_i,T_i) $ 消失

  • 求子樹 $ x $ 中,等於 $ w[x]+d[x] $ 的 $ A $ 類數個數+等於 $ w[x]-d[x] $ 的 $ B $ 類數個數

  • 全局數組計數,統計遍歷子樹x前後相應位置上的差
     

  • 我們使用兩個全局的桶數組(因為這代表了 $ S_i-lca(S_i,T_i) $ 和 $ lca(S_i,T_i)-T_i $ 的兩端)
    來保存 $ d[S_i] $ 和 $ d[S_i]-2*d[lca(S_i,T_i)] $ 的次數

  • 在遞歸進入一個節點 $ u $ 時,我們先記錄下原本滿足條件的個數,最後差分操作完成後用新增的個數減去原來的個數,就是新產生的答案數

  • 註意在 $ B $ 類數 $ w[x]-d[x] $ 中可能出現負數的情況,桶數組要全體 $ +n $
     

代碼

/**************************************************************
    Problem: 2124
    User: PotremZ
    Language: C++
    Result: Accepted
    Time:1176 ms
    Memory:1564 kb
****************************************************************/

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define maxn 300010
#define ri register int
vector<int>G[maxn],a1[maxn],a2[maxn],b1[maxn],b2[maxn];
int n,m,ans[maxn],dep[maxn],fa[maxn][21],sum1[maxn],sum2[maxn<<1],w[maxn];
void dfs(int u){
    for(ri v,i=0;i<G[u].size();++i)
        if(G[u][i]!=fa[u][0]){
            v=G[u][i];
            dep[v]=dep[u]+1; fa[v][0]=u;
            for(int i=1;(1<<i)<=dep[v];++i) fa[v][i]=fa[fa[v][i-1]][i-1];
            dfs(v);
        }
}
inline int lca(int u,int v){
    if(dep[u]>dep[v]) swap(u,v);
    for(ri i=20;~i;--i)
        if(dep[u]<=dep[v]-(1<<i)) v=fa[v][i];
    if(u==v) return u;
    for(ri i=20;~i;--i)
        if(fa[u][i]!=fa[v][i]){ u=fa[u][i]; v=fa[v][i]; }
    return fa[u][0];
}
void dfs2(int u){
    /記錄原本的個數
    int tmp1=sum1[dep[u]+w[u]],tmp2=sum2[w[u]-dep[u]+n];
    for(ri i=0;i<G[u].size();++i)
        if(G[u][i]!=fa[u][0]) dfs2(G[u][i]);
    for(int i=0;i<a1[u].size();++i) ++sum1[a1[u][i]];
    for(int i=0;i<a2[u].size();++i) --sum1[a2[u][i]];
    for(int i=0;i<b1[u].size();++i) ++sum2[b1[u][i]+n];
    for(int i=0;i<b2[u].size();++i) --sum2[b2[u][i]+n];
    ans[u]+=sum1[dep[u]+w[u]]-tmp1+sum2[w[u]-dep[u]+n]-tmp2;
    //差分後子樹u的答案增加了新滿足條件的數的個數
}
int main(){
    scanf("%d %d",&n,&m);
    for(ri i=1;i<n;++i){
        int u,v;
        scanf("%d %d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1);
    for(ri i=1;i<=n;++i) scanf("%d",&w[i]);
    for(ri i=1;i<=m;++i){
        int s,t;
        scanf("%d %d",&s,&t);
        int LCA=lca(s,t);
        int g=dep[s]-2*dep[LCA];
        a1[s].push_back(dep[s]);
        a2[fa[LCA][0]].push_back(dep[s]);
        b1[t].push_back(g);
        b2[LCA].push_back(g);
        //記錄A類數和B類數出現和消失的位置
    }
    dfs2(1);
    for(int i=1;i<=n;++i) printf("%d ",ans[i]);
    return 0;
}

[Noip2016]天天愛跑步 樹上差分