P1600 [NOIP2016 提高組] 天天愛跑步
題目
題目描述
小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\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\) 人被觀察到。
【子任務】
每個測試點的資料規模及特點如下表所示。
提示: 資料範圍的個位上的數字可以幫助判斷是哪一種資料型別。
思路
part.0問題簡化
這裡提供一種線段樹合併的做法.
我們把一條路徑\(s\to t\)拆開,分為\(s\to lca(s,t)\)和\(lca(s,t)\to t\)這兩部分.這樣相當於一個玩家,只會從低深度跑向高深度或從高深度跑向低深度.
part.1高深度跑向低深度
先考慮\(s\to lca(s,t)\)這條路徑,即高深度跑向低深度.(樹上問題中,從子結點到父結點的問題處理起來往往會更簡單,因為一個子結點只有一個父結點,一個父結點對應多個子結點)
這條路徑什麼時候能對一個點\(u\)產生貢獻?
首先,\(u\)得在那條路徑上,所以,\(u\)是\(s\)的祖先結點.
其次\(dist(s,u)=w_u\)即\(deep(s)-deep(u)=w_u\) , \(w_u +de ep(u)=deep(s)\).(\(dist\)表示樹上兩點距離,\(deep\)即深度).
我們採用樹上差分:定義陣列\(a,b\).
對於一條路徑\(s\to lca(s,t)\)(路徑對\(lca\)的貢獻已經在這裡計算,在part.2中就不在計算)
\[b_{s,deep(s)}=b_{s,deep(s)}+1\\ b_{father(lca),deep(s)}=b_{father(lca),deep(s)}-1 \]對於樹上每一個結點\(u\)
\[a_{u,i}=b_{u,i}+\sum _{v\in son(i)}b_{v,i} \]而\(u\)點的答案就是\(a_{u,w_u+deep(u)}\).
程式碼大概是這樣:
void calc1(int u) {
for(int i = head[u] ; i ; i = ed[i].nxt) {
int v = ed[i].to;
if(v == father[u])
continue;
calc1(v);
SegT::root[u] = SegT::merge(SegT::root[u] , SegT::root[v] , 0 , n * 2);
}
ans[u] += SegT::query(SegT::root[u] , dep(u) + w[u]);
}
//in main
for(int i = 1 ; i <= m ; i++)
s[i] = read() , t[i] = read() , lca[i] = LCA::lca(s[i] , t[i]);
for(int i = 1 ; i <= n ; i++)
SegT::root[i] = SegT::newnode(0 , n * 2);
for(int i = 1 ; i <= m ; i++) {//s->lca
SegT::change(SegT::root[s[i]] , dep(s[i]) , 1);
SegT::change(SegT::root[father[lca[i]]] , dep(s[i]) , -1);
}
calc1(root);
part.2低深度跑向高深度
對於路徑\(lca(s,t)\to t\)是從低深度跑向高深度,如果我們從上到下統計答案,是很麻煩的.如果我們仍用差分,改一下\(lca\)的差分陣列,整個子樹都要受到影響,而不是我們所希望的\(lca(s,t)\to t\)這條路徑.
所以,我們考慮能不能像上面一樣,子結點先算出答案,然後合併到父結點,算出父結點的答案.
再看下線段樹的change
函式:void change(int p , int pos , int dat)
,\(p\)是當前子樹的根節點編號,是不能做什麼手腳的了,\(dat\)是差分的資料,一般只會用1
和-1
,最有希望能幫我們實現目的的應該是\(pos\).
那我們應該如何設計\(pos\)呢?
想想part.1中為什麼可以?\(w_u+deep(u)=deep(s)\),這個等式中,左邊只跟結點\(u\)有關,右邊只跟結點\(s\)有關,所以等式左邊用於統計答案,右邊用於初始化差分陣列.
同樣的,如果我們在part.2中也找到一個類似的等式,一邊只和\(u\)有關,另一邊只和\(s,t,lca\)有關,我們的目的就達到了.
應該不難想到有一個這樣的等式:
\[\text{u到s的路徑長度}=deep(u)+deep(s)-2\cdot deep(lca(u,s))=w_u \]又因為\(u\)在路徑\(lca(s,t)\to t\)上,所以\(lca(u,s)=lca(s,t)\),同時,移項可以得到:
\[w_u-deep(u)=deep(s)-2\cdot deep(lca(s,t)) \]為了防止出現負數(如果有負數,線上段樹遞迴左右子樹時會出現鬼畜),我們兩邊同時+n
.
所以程式碼就出來啦.
void calc2(int u) {
for(int i = head[u] ; i ; i = ed[i].nxt) {
int v = ed[i].to;
if(v == father[u])
continue;
calc2(v);
SegT::root[u] = SegT::merge(SegT::root[u] , SegT::root[v] , 0 , n * 2);
}
ans[u] += SegT::query(SegT::root[u] , w[u] - dep(u) + n);
}
//in main
SegT::clear();//清空線段樹
for(int i = 1 ; i <= n ; i++)
SegT::root[i] = SegT::newnode(0 , n * 2);
for(int i = 1 ; i <= m ; i++) {//lca->t
SegT::change(SegT::root[t[i]] , dep(s[i]) - 2 * dep(lca[i]) + n , 1);
SegT::change(SegT::root[lca[i]] , dep(s[i]) - 2 * dep(lca[i]) + n , -1);
}
calc2(root);
程式碼
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;
int read() {
int re = 0;
char c = getchar();
bool negt = false;
while(c < '0' || c > '9')
negt |= (c == '-') , c = getchar();
while(c >= '0' && c <= '9')
re = (re << 1) + (re << 3) + c - '0' , c = getchar();
return negt ? -re : re;
}
const int N = 300010;
const int maxZ = 100000;
const int M = 300010;
const int M_logZ = M * 20;
struct EDGE {
int to , nxt;
} ed[N * 2];
int head[N];
void addedge(int u , int v) {
static int cnt;
++cnt;
ed[cnt].to = v , ed[cnt].nxt = head[u] , head[u] = cnt;
}
int n , m , root;
int father[N];
namespace LCA {//RMQ-LCA
int cnt = 0;
int dep[N * 2] , id[N * 2] , first[N];
int st[N * 4][25];
void dfs(int u , int nowdep) {
++cnt;
id[cnt] = u , dep[cnt] = nowdep , first[u] = cnt;
for(int i = head[u] ; i ; i = ed[i].nxt) {
int v = ed[i].to;
if(v == father[u])
continue;
father[v] = u;
dfs(v , nowdep + 1);
++cnt;
id[cnt] = u , dep[cnt] = nowdep;
}
}
void init(int root) {
dfs(root , 1);
for(int i = 1 ; i <= cnt ; i++)
st[i][0] = i;
int k = log(n) / log(2) + 1;
dep[0] = 0x3fffffff;
for(int j = 1 ; j <= k ; j++)
for(int i = 1 ; i <= cnt ; i++)
st[i][j] = dep[st[i][j - 1]] < dep[st[i + (1 << j - 1)][j - 1] ] ? st[i][j - 1] : st[i + (1 << j - 1)][j - 1];
}
int lca(int u , int v) {
u = first[u] , v = first[v];
if(u > v) {
int tmp;
tmp = u , u = v , v = tmp;
}
int k = log(v - u + 1) / log(2);
return id[
dep[st[u][k]] < dep[st[v - (1 << k) + 1][k]] ? st[u][k] : st[v - (1 << k) + 1][k]
];
}
}
inline int dep(int u) {
return LCA::dep[LCA::first[u]];
}
const int M_logN = M * 20;
namespace SegT {//線段樹,帶合併
int root[N];
struct NodeClass {
int l , r , ls , rs , dat;
} node[M_logN];
inline int newnode(int l , int r) {
static int cnt = 0;
if(l == -1 && r == -1) {
cnt = 0;
return 0;
}
++cnt;
node[cnt].l = l , node[cnt].r = r , node[cnt].dat = 0;
return cnt;
}
void change(int p , int pos , int dat) {
if(node[p].l == node[p].r)
node[p].dat += dat;
else {
int l = node[p].l , r = node[p].r;
int mid = (l + r) / 2;
if(l + r < 0) --mid;
if(pos <= mid)
change(node[p].ls = (node[p].ls == 0 ? newnode(l , mid) : node[p].ls) , pos , dat);
else
change(node[p].rs = (node[p].rs == 0 ? newnode(mid + 1 , r) : node[p].rs) , pos , dat);
}
}
int query(int p , int pos) {
if(p == 0)
return 0;
if(node[p].l == node[p].r)
return node[p].dat;
int mid = (node[p].l + node[p].r) / 2;
if(node[p].l + node[p].r < 0)--mid;
if(pos <= mid)
query(node[p].ls , pos);
else
query(node[p].rs , pos);
}
int merge(int p1 , int p2 , int l , int r) {
if(p1 == 0 || p2 == 0)
return p1 == 0 ? p2 : p1;
if(l == r) {
node[p1].dat += node[p2].dat;
return p1;
}
int mid = (l + r) / 2;
if(l + r < 0)--mid;
node[p1].ls = merge(node[p1].ls , node[p2].ls , l , mid);
node[p1].rs = merge(node[p1].rs , node[p2].rs , mid + 1 , r);
return p1;
}
void clear() {
memset(root , 0 , sizeof(root));
memset(node , 0 , sizeof(node));
newnode(-1 , -1);
}
}
int ans[N];
int s[N] , t[N] , lca[N];
int w[N];
void calc1(int u) {
for(int i = head[u] ; i ; i = ed[i].nxt) {
int v = ed[i].to;
if(v == father[u])
continue;
calc1(v);
SegT::root[u] = SegT::merge(SegT::root[u] , SegT::root[v] , 0 , n * 2);
}
ans[u] += SegT::query(SegT::root[u] , dep(u) + w[u]);
}
void calc2(int u) {
for(int i = head[u] ; i ; i = ed[i].nxt) {
int v = ed[i].to;
if(v == father[u])
continue;
calc2(v);
SegT::root[u] = SegT::merge(SegT::root[u] , SegT::root[v] , 0 , n * 2);
}
ans[u] += SegT::query(SegT::root[u] , w[u] - dep(u) + n);
}
int main() {
n = read() , m = read() , root = 1;
for(int i = 1 ; i < n ; i++) {
int u = read() , v = read();
addedge(u , v) , addedge(v , u);
}
LCA::init(root);
for(int i = 1 ; i <= n ; i++)
w[i] = read();
for(int i = 1 ; i <= m ; i++)
s[i] = read() , t[i] = read() , lca[i] = LCA::lca(s[i] , t[i]);
for(int i = 1 ; i <= n ; i++)
SegT::root[i] = SegT::newnode(0 , n * 2);
for(int i = 1 ; i <= m ; i++) {//s->lca
SegT::change(SegT::root[s[i]] , dep(s[i]) , 1);
SegT::change(SegT::root[father[lca[i]]] , dep(s[i]) , -1);
}
calc1(root);
SegT::clear();
for(int i = 1 ; i <= n ; i++)
SegT::root[i] = SegT::newnode(0 , n * 2);
for(int i = 1 ; i <= m ; i++) {//lca->t
SegT::change(SegT::root[t[i]] , dep(s[i]) - 2 * dep(lca[i]) + n , 1);//+n,防止區間包含負數時出現鬼畜
SegT::change(SegT::root[lca[i]] , dep(s[i]) - 2 * dep(lca[i]) + n , -1);
}
calc2(root);
for(int i = 1 ; i <= n ; i++)
printf("%d " , ans[i]);
return 0;
}