树上跑步【欧拉序】【结论】

>Link

ybtoj树上跑步


>Description

小 A 每天会在某一个以 1 为根、N 个结点的有根树上跑步,初始时,每个结点都有一个障碍物,每个障碍物会按照深度优先的顺序周期性地在以初始点为根的子树中移动,每个单位时间只会移动一条边。

小 A 初始在 x 点,他要沿着最短路径跑到根结点,且每个单位时间只会移动一条边。在这个过程中,如果小 A 在一个结点遇到了障碍物,他需要消耗 点能量清除它,被清除的障碍物不会再出现。

请你求出小 A 需要消耗多少能量才能到终点。

n ≤ 5 ∗ 1 0 5 n\le 5*10^5 n5105


>解题思路

研究一下题目的性质,发现只有 x 到 1 路径上的点才可能与小A相遇,而且如果第一次运动周期没有遇到,那以后都不会遇到了(一次运动就把它下面的所有点走了一遍,没有遇到说明小A已经搜到它的上面了)
所以我们只用考虑初始在 x 到 1 路径上的点,而且不用考虑周期运动,研究第一次运动就行了

我们把 1 号节点的第一次运动周期的轨迹列出来,显然所有节点的运动轨迹都包含在里面
而且我们发现它是深搜所有的点,遇到加入、搜完它的一个子树加入,一个点在数列中出现的次数为它的度数
其实这就是欧拉序(还有一种欧拉序是,遇到加入、搜完它的所有子树加入,出现次数为2)

把这棵树的欧拉序求出来后,我们就可以开始找需要清除的障碍物了
从 x 开始往上跳,遇到一个点,假设在这个点与一些障碍物相遇了,我们已经知道了时间,就可以在欧拉序上找到这些障碍物原始的位置。如果他们一开始在 x 到 1 的路径上,说明这些障碍物是会被遇到的
注意如果我们要清除障碍物,要在它们原始的位置上标记一下,防止一个障碍物被重复清除
而且遇到一个点,我们要找它在欧拉序上的所有位置,因为可能有多个障碍物在一个位置出现,都要清除


>代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define N 500010
using namespace std;

vector<int> pos[N];
struct edge
{
    
    
	int to, nxt;
} e[N];
int T, n, m, cnt, h[N], pp[N], q[N * 2], tot, dep[N], fa[N], x, ans;
bool ok[N], fst[N * 2];

void dfs (int now, int fath)
{
    
    
	q[++tot] = now, fst[tot] = 1;
	pos[now].push_back (tot);
	fa[now] = fath;
	dep[now] = dep[fath] + 1;
	for (int i = h[now]; i; i = e[i].nxt)
	{
    
    
		dfs (e[i].to, now);
		q[++tot] = now;
		pos[now].push_back (tot);
	}
}
void work ()
{
    
    
	scanf ("%d", &n);
	for (int i = 1; i <= n; i++)
	{
    
    
		scanf ("%d", &m);
		for (int j = 1; j <= m; j++) scanf ("%d", &pp[j]);
		for (int j = m; j >= 1; j--)
		  e[++cnt] = (edge){
    
    pp[j], h[i]}, h[i] = cnt;
	}
	dfs (1, 0);
	scanf ("%d", &x);
//	for (int i = 1; i <= tot; i++) printf ("%d ", q[i]);
//	printf ("\n");
	int now = x, tim, y, len;
	for (; now; now = fa[now]) ok[now] = 1;
	now = x;
	for (; now; now = fa[now])
	{
    
    
		tim = dep[x] - dep[now];
		len = pos[now].size();
		for (int i = 0; i < len; i++)
		{
    
    
			y = pos[now][i] - tim;
			if (y <= 0 || !ok[q[y]] || !fst[y]) continue;
			ok[q[y]] = 0, ans++;
		}
	}
	printf ("%d\n", ans);
	for (int i = 1; i <= n; i++)
	  h[i] = fa[i] = dep[i] = ok[i] = 0;
	for (int i = 1; i <= tot; i++) q[i] = fst[i] = 0;
	for (int i = 1; i <= n; i++)
	  while (!pos[i].empty()) pos[i].pop_back();
	cnt = tot = ans = 0;
}

int main()
{
    
    
	freopen ("run.in", "r", stdin);
	freopen ("run.out", "w", stdout);
	scanf ("%d", &T);
	while (T--)
	  work ();
	return 0;
}

おすすめ

転載: blog.csdn.net/qq_43010386/article/details/121220168