天天愛跑步
題目:天天愛跑步
網址:https://www.luogu.com.cn/problem/P1600
題目描述
小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\)
輸入輸出樣例
輸入 #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″, 以瞭解在最終評測時棧空問的限制與在當前工作環境下調整棧空問限制的方法。
在最終評測時,呼叫棧佔用的空間大小不會有單獨的限制,但在我們的工作環境中預設會有 \(1 \text{MiB}\) 的限制。 這可能會引起函式呼叫層數較多時, 程式發生棧溢位崩潰。
我們可以使用一些方法修改呼叫棧的大小限制。 例如, 在終端中輸入下列命令 ulimit -s 1048576
此命令的意義是,將呼叫棧的大小限制修改為 \(1 \text{GiB}\)。
例如,在選手目錄建立如下 sample.cpp 或 sample.pas
將上述原始碼編譯為可執行檔案 sample 後,可以在終端中執行如下命令執行該程式
./sample
如果在沒有使用命令“ ulimit -s 1048576”的情況下執行該程式, sample 會因為棧溢位而崩潰; 如果使用了上述命令後執行該程式,該程式則不會崩潰。
特別地, 當你開啟多個終端時, 它們並不會共享該命令, 你需要分別對它們執行該命令。
請注意, 呼叫棧佔用的空間會計入總空間佔用中, 和程式其他部分佔用的記憶體共同受到記憶體限制。
記起點和終點的最近公共祖先為\(lca(s_i,t_i)\)。
從\(s_i\)走到\(lca(s_i,t_i)\)再從\(lca(s_i,t_i)\)走到\(t_i\)。
只需考慮\(s_i\)到\(lca(s_i,t_i)\)的路徑即可;
對於路徑上的一點,觀察員能夠觀察到的條件為:\(dep[s_i]-dep[x]==w[x]\);
移過去,得\(dep[s_i]==dep[x]+w[x]\);
然後再等價轉換該式,相當於放物品,統計指定型號的物品數量。
接著就動態開點線段數合併即可。
不過更簡便的方式是:差分求總和。
C ++ AC程式碼
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
using namespace std;
const int SIZE = 300000 + 5, delta = 600000;
vector <int> G[SIZE];
vector <int> query[SIZE];
queue <int> Q;
int n, m, t = 0, c[SIZE << 4], w[SIZE], F[SIZE][40], dep[SIZE];
int ans[SIZE];
void BFS()
{
while(!Q.empty()) Q.pop();
memset(dep, 0, sizeof(dep));
Q.push(1);
dep[1] = 1;
F[1][0] = 0;
while(Q.size())
{
int u = Q.front();
Q.pop();
for(int i = 0; i < G[u].size(); ++ i)
{
int v = G[u][i];
if(dep[v]) continue;
dep[v] = dep[u] + 1;
F[v][0] = u;
Q.push(v);
for(int i = 1; i <= t; ++ i) F[v][i] = F[F[v][i - 1]][i - 1];
}
}
return;
}
int LCA(int x, int y)
{
if(dep[x] > dep[y]) swap(x, y);
for(int i = t; i >= 0; -- i)
if(dep[F[y][i]] >= dep[x]) y = F[y][i];
if(y == x) return x;
for(int i = t; i >= 0; -- i)
{
if(F[x][i] != F[y][i]) x = F[x][i], y = F[y][i];
}
return F[y][0];
}
void dfs(int u)
{
int S1 = c[dep[u] + w[u]] + c[w[u] - dep[u] + delta];
for(int i = 0; i < query[u].size(); ++ i)
{
if(query[u][i] < 0) -- c[-query[u][i]];
else ++ c[query[u][i]];
}
for(int i = 0; i < G[u].size(); ++ i)
{
int v = G[u][i];
if(v != F[u][0]) dfs(v);
}
int S2 = c[dep[u] + w[u]] + c[w[u] - dep[u] + delta];
ans[u] = S2 - S1;
return;
}
int main()
{
scanf("%d %d", &n, &m);
t = log(n) / log(2);
for(int i = 1; i < n; ++ i)
{
int x, y;
scanf("%d %d", &x, &y);
G[x].push_back(y), G[y].push_back(x);
}
BFS();
for(int i = 1; i <= n; ++ i) scanf("%d", &w[i]);
int lca;
for(int i = 0; i < m; ++ i)
{
int u, v;
scanf("%d %d", &u, &v);
lca = LCA(u, v);
query[u].push_back(dep[u]), query[F[lca][0]].push_back(-dep[u]);
query[v].push_back(dep[u] - 2 * dep[lca] + delta), query[lca].push_back(2 * dep[lca] - dep[u] - delta);
}
memset(c, 0, sizeof(c));
dfs(1);
for(int i = 1; i <= n; ++ i) printf("%d ", ans[i]);
printf("\n");
return 0;
}
總結回顧
參考文獻
- 《演算法競賽進階指南》
- https://www.luogu.com.cn/problem/solution/P1600