题目
一棵树,每个点有一个障碍物,障碍物会按照父 → \to →子树1 → \to →父 → \to →子树2 → \to →父 → ⋯ → \to \cdots \to →⋯→父的顺序为周期移动(1秒一次),一个人从 x x x节点出发,每秒会向父节点移动一次,问移动到根节点会遇到多少个障碍物.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7gCTSBl6-1636341914385)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211108104439427.png)]
思路
性质:如果遇到障碍物,仅会在该障碍物运动的第一个周期内相遇.
另外,假如你写过RMQ版的LCA,你会知道初始在 i i i的障碍物,一个周期的长度为:以 i i i为根的子树大小乘2减1(除根外,每个点产生自己和父亲两次贡献).
如图,3号点的障碍物有从1号传下来的和3号自带的.两个障碍物第一次出现在3号的时间为0,1
,然后两个障碍物遍历4号,又回到3号,所以,第二次出现的时间为2,3
,同理,第3次出现的时间为4,5
.如果5号为出发点,我们检查一下时间为1时3号有无障碍物即可.
注意到这些障碍物的移动是一样的,所以一个周期内对于相同的 i i i,不同障碍物第 i i i, i + 1 i+1 i+1次遍历同一个点的时间差一样.
我们依次遍历根到出发点路径上的每一个点,维和一个集合 T T T,表示根到当前路径上所有的障碍物第一次出现在当前点的时间的集合,每向下走一步,相当于 T T T内所有数一起加上一个数,再把0放到集合中.
由于是同时加上相同的数,我们可以用一个类似懒标的东西维护.
时间复杂度可以做到 O ( n ) O(n) 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;
}