1. 程式人生 > >[NOIP2016 DAY1 T2]天天愛跑步-[差分+線段樹合並][解題報告]

[NOIP2016 DAY1 T2]天天愛跑步-[差分+線段樹合並][解題報告]

rip top 偏移 digi http n-1 sdi git +=

[NOIP2016 DAY1 T2]天天愛跑步


題面:

B【NOIP2016 DAY1】天天愛跑步
時間限制 : - MS 空間限制 : 565536 KB 評測說明 : 2s

Description

小c同學認為跑步非常有趣,於是決定制作一款叫做《天天愛跑步》的遊戲。《天天愛跑步》是一個養成類遊戲,需要

玩家每天按時上線,完成打卡任務。這個遊戲的地圖可以看作一一棵包含 \(N\)個結點和\(N-1\) 條邊的樹, 每條邊連接兩個結點,且任意兩個結點存在一條路徑互相可達。樹上結點編號為從1到N的連續正整數。現在有個玩家,第個玩家的起點為\(Si\) ,終點為\(Ti\) 。每天打卡任務開始時,所有玩家在第\(0\)

秒同時從自己的起點出發, 以每秒跑一條邊的速度,不間斷地沿著最短路徑向著自己的終點跑去, 跑到終點後該玩家就算完成了打卡任務。

(由於地圖是一棵樹, 所以每個人的路徑是唯一的)小C想知道遊戲的活躍度, 所以在每個結點上都放置了一個觀察員。 在結點的觀察員會選擇在第\(W_j\)秒觀察玩家, 一個玩家能被這個觀察員觀察到當且僅當該玩家在第\(W_j\)秒也理到達了結點\(J\)

小C想知道每個觀察員會觀察到多少人?註意: 我們認為一個玩家到達自己的終點後該玩家就會結束遊戲, 他不能等待一 段時間後再被觀察員觀察到。 即對於把結點\(J\)作為終點的玩家: 若他在第\(W_j\)秒重到達終點,則在結點\(J\)

的觀察員不能觀察到該玩家;若他正好在第\(W_j\)秒到達終點,則在結點的觀察員可以觀察到這個玩家。

Input

第一行有兩個整數\(N\)\(M\) 。其中\(N\)代表樹的結點數量, 同時也是觀察員的數量, \(M\)代表玩家的數量。

接下來\(n-1\) 行每行兩個整數\(U\)\(V\) ,表示結點\(U\)到結點\(V\)有一條邊。

接下來一行\(N\) 個整數,其中第個整數為\(W_j\), 表示結點出現觀察員的時間。

接下來 \(M\)行,每行兩個整數\(S_i\)\(T_i\)表示一個玩家的起點和終點。

對於所有的數據,保證 。

\(1<=S_i,T_i<=N,0<=W_j<=N\)

Output

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

Sample Input

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

Sample Output

2 0 0 1 1 1

HINT

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

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

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

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

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

對於\(6\)號點,玩家\(3\)被觀察到,共\(1\)人被觀察到


題解:

為了這道題還專門去學習了線段樹合並(暴露了蒟蒻現在才會這東西的事實),有空也會寫一篇關於線段樹合並的博客,這篇博客將不對其原理進行解釋(這都挖了幾個坑了啊)(ps:已秒填坑)

我們考慮對一個節點\(i\)的子樹中如果含有起點\(x\),我們很容易得到這樣的式子:\(dep[x]-dep[i]==w[i]\),(\(w[i]\)是這個節點觀察員出現的時間),我們一般希望將等式兩邊處理為一個相對的定值(不知道怎麽表達這種感覺,意會意會),推出\(dep[i]+w[i]==dep[x]\),對於這個式子,左邊對觀察者就是定值,右邊對於跑步者就是定值;

同樣,當終點\(x\)在節點\(i\)的子樹裏時,我們需要將時間的影響考慮進去,記起點與中點的距離為\(dis\),那麽\(w[i]==dep[i]-dep[x]+dis\),移項變為\(w[i]-dep[i]==dis-dep[x]\),對這個式子來說,依舊是:左邊對觀察者是定值,右邊對於跑步者是定值;

\(Start:dep[i]+w[i]==dep[Start]\)

\(End:w[i]-dep[i]==dis-dep[End]\)

於是我們的問題就轉換為求一顆子樹中,對這個子樹的根所能做的貢獻之和,即求子樹中滿足根節點為\(i\)時的節點個數(當然這個節點可能會同時有幾個權值),於是,我們考慮對每一個點維護權值線段樹(每個點維護兩個,分別記錄起點終點),表示自己所包含子樹中所含的每個值的點個數為多少;

考慮使用樹剖的思想,將起點到終點的過程分解為從起點到\(LCA\),從\(LCA\)到終點這兩條鏈,這時候我們就可以顯然的使用差分的思想,在起點的起點樹(對一個點開兩個線段樹,記記錄起點的叫起點樹,另一個為終點樹)的\(dep[Start]\)的位置\(+1\),\(Father_LCA\)\(dep[Start]\)位置的起點樹\(-1\),在終點終點樹\(dis-dep[End]\)的位置\(+1\),在\(LCA\)\(dis-dep[End]\)的位置\(-1\),這就是對序列差分的原理;

\(DFS\)統計答案,從葉節點開始,統計完就向上合並即可;

註意權值線段樹可能出現負數,需要偏移;

\(code:\)

#include<cstdio>
#include<ctype.h>
#include<vector>
#include<algorithm>
using namespace std;

char buf[1<<20],*p1,*p2;
inline char gc()
{
//    return getchar();
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin))==p1?0:*p1++;
}

template<typename T>
inline void read(T &x)
{
    char tt;
    bool flag=0;
    while(!isdigit(tt=gc())&&tt!='-');
    tt=='-'?(flag=1,x=0):(x=tt-'0');
    while(isdigit(tt=gc())) x=x*10+tt-'0';
    if(flag) x=-x;
}

const int maxn=3*(1e5+2);
int sum[maxn*40][2],ls[maxn*40][2],rs[maxn*40][2],tot[2],root[maxn*40][2];
int n,m,w[maxn],mx,ans[maxn];
int son[maxn],top[maxn],sz[maxn],dep[maxn],fa[maxn];
vector<int>G[maxn];

int getlca(int x,int y)
{
    if(x==y) return x;
    while(top[x]^top[y])
    dep[top[x]]>=dep[top[y]]?x=fa[top[x]]:y=fa[top[y]];
    return dep[x]<=dep[y]?x:y;
}

int cal(int x,int y,int lca)
{
    return dep[x]+dep[y]-(dep[lca]<<1);
}

void modify(int &p,int l,int r,int x,int d,int id)
{
    if(!p) p=++tot[id];
    if(l==r){sum[p][id]+=d;return;}
    int mid=l+r>>1;
    if(x<=mid) modify(ls[p][id],l,mid,x,d,id);
    if(x>mid) modify(rs[p][id],mid+1,r,x,d,id);
}

int query(int &p,int l,int r,int x,int id)
{
    if(!p) p=++tot[id];
    if(l==r){return sum[p][id];}
    int mid=l+r>>1;
    if(x<=mid) return query(ls[p][id],l,mid,x,id);
    if(x>mid) return query(rs[p][id],mid+1,r,x,id);
}

void merge(int &x,int y,int id)
{
    if(!x||!y){x=x+y;return;}
    sum[x][id]+=sum[y][id];
    merge(ls[x][id],ls[y][id],id);
    merge(rs[x][id],rs[y][id],id);
}

void dfs(int x,int pre)
{
    sz[x]++,dep[x]=dep[pre]+1,fa[x]=pre;
    for(int i=G[x].size()-1;i>=0;i--)
    {
        int p=G[x][i];
        if(p==pre) continue;
        dfs(p,x);
        if(sz[p]>sz[son[x]]) son[x]=p;
        sz[x]+=sz[p];
    }
}

void dfs(int x,int pre,int t)
{
    top[x]=t;
    if(!son[x]) return;
    dfs(son[x],x,t);
    for(int i=G[x].size()-1;i>=0;i--)
    {
        int p=G[x][i];
        if(p==pre||p==son[x]) continue;
        dfs(p,x,p);
    }
}

void dfs(int x)
{
    for(int i=G[x].size()-1;i>=0;i--)
    {
        int p=G[x][i];
        if(p==fa[x]) continue;
        dfs(p);
        merge(root[x][0],root[p][0],0);
        merge(root[x][1],root[p][1],1);
    }
    ans[x]+=query(root[x][0],1,mx,dep[x]+w[x],0);
    ans[x]+=query(root[x][1],1,mx,w[x]-dep[x]+maxn,1);
}

int main()
{
    read(n),read(m);mx=n+maxn;
    for(int i=1;i<n;i++)
    {
        int x,y;
        read(x),read(y);
        G[x].push_back(y);    
        G[y].push_back(x);
    }dfs(1,0),dfs(1,0,1);
    for(int i=1;i<=n;i++) read(w[i]);
    for(int i=1;i<=m;i++)
    {
        int x,y;
        read(x),read(y);
        int lca=getlca(x,y);
        int t=cal(x,y,lca);
        modify(root[x][0],1,mx,dep[x],1,0);
        modify(root[fa[lca]][0],1,mx,dep[x],-1,0);
        
        modify(root[y][1],1,mx,t-dep[y]+maxn,1,1);
        modify(root[lca][1],1,mx,t-dep[y]+maxn,-1,1);
    }dfs(1);
    for(int i=1;i<=n;i++)
    printf("%d ",ans[i]);
}

[NOIP2016 DAY1 T2]天天愛跑步-[差分+線段樹合並][解題報告]