提高模擬賽Day8T3樹上跑步
阿新 • • 發佈:2021-11-08
@
目錄題目
一棵樹,每個點有一個障礙物,障礙物會按照父\(\to\)子樹1\(\to\)父\(\to\)子樹2\(\to\)父\(\to \cdots \to\)父的順序為週期移動(1秒一次),一個人從\(x\)節點出發,每秒會向父節點移動一次,問移動到根節點會遇到多少個障礙物.
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-7gCTSBl6-1636341914385)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211108104439427.png)]
思路
性質:如果遇到障礙物,僅會在該障礙物運動的第一個週期內相遇.
另外,假如你寫過RMQ版的LCA,你會知道初始在\(i\)的障礙物,一個週期的長度為:以\(i\)為根的子樹大小乘2減1(除根外,每個點產生自己和父親兩次貢獻).
如圖,3號點的障礙物有從1號傳下來的和3號自帶的.兩個障礙物第一次出現在3號的時間為0,1
,然後兩個障礙物遍歷4號,又回到3號,所以,第二次出現的時間為2,3
,同理,第3次出現的時間為4,5
.如果5號為出發點,我們檢查一下時間為1時3號有無障礙物即可.
注意到這些障礙物的移動是一樣的,所以一個週期內對於相同的\(i\),不同障礙物第\(i\),\(i+1\)次遍歷同一個點的時間差一樣.
我們依次遍歷根到出發點路徑上的每一個點,維和一個集合\(T\),表示根到當前路徑上所有的障礙物第一次出現在當前點的時間的集合,每向下走一步,相當於\(T\)內所有數一起加上一個數,再把0放到集合中.
由於是同時加上相同的數,我們可以用一個類似懶標的東西維護.
時間複雜度可以做到\(O(n)\).
程式碼
#include <iostream> #include <cstdio> #include <vector> #include <unordered_map> #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 = 5e5 + 10; int n; int siz[N] , fa[N]; int start; bool flag[N]; int dis[N]; vector<int> son[N]; vector<int> vis;//vis:根到出發點的路徑經過的點 void dfs(int x) { for(auto to : son[x]) dfs(to) , siz[x] += siz[to]; ++siz[x]; } unordered_map <int , int> mp; inline int mp_find(int val) { auto p = mp.find(val); return p == mp.end() ? 0 : p->second; } void solve() { memset(siz , 0 , sizeof(siz)) , memset(flag , 0 , sizeof(flag)) , memset(dis , 0 , sizeof(dis)) , memset(fa , 0 , sizeof(fa)) , vis.clear(); mp.clear(); n = read(); for(int i = 1 ; i <= n ; i++)son[i].clear(); for(int i = 1 ; i <= n ; i++) for(int j = read() , to ; j > 0 ; j--) son[i].push_back(to = read()) , fa[to] = i; dis[start = read()] = 0; while(start != 0) { flag[start] = true; dis[fa[start]] = dis[start] + 1; vis.push_back(start); start = fa[start]; } for(int i = 0 , j = vis.size() - 1 ; i < j ; i++ , j--)swap(vis[i] , vis[j]); dfs(1); int lzyTag = -1; int ans = 0; for(int u : vis) { int sum = 0; ++lzyTag; mp[-lzyTag]++; for(int v : son[u]) {//從u遍歷一棵子樹,就會再回到一次u節點 if(flag[v])break; sum += siz[v] * 2; ans += mp_find(dis[u] - sum - lzyTag); } ans += mp_find(dis[u] - lzyTag); lzyTag += sum; } cout << ans << endl; } int main() { freopen("run.in" , "r" , stdin); freopen("run.out" , "w" , stdout); int T = read(); while(T--)solve(); return 0; }