【题解】[USACO08DEC-Gold] Trick or Treat on the Farm

题目信息

题目来源:USACO 2008 December Gold;

翻译来源:5ab [输入格式、输出格式、数据规模],洛谷 [题目描述];

在线评测地址:Luogu#2921

运行限制:(1.00 extrm{s}/128 extrm{MiB})

题目描述

Every year in Wisconsin the cows celebrate the USA autumn holiday of Halloween by dressing up in costumes and collecting candy that Farmer John leaves in the N (1 <= N <= 100,000) stalls conveniently numbered 1..N.

每年,在威斯康星州,奶牛们都会穿上衣服,收集农夫约翰在 (N) 个牛棚隔间中留下的糖果,以此来庆祝美国秋天的万圣节。

Because the barn is not so large, FJ makes sure the cows extend their fun by specifying a traversal route the cows must follow. To implement this scheme for traveling back and forth through the barn, FJ has posted a 'next stall number' next_i (1 <= next_i <= N) on stall i that tells the cows which stall to visit next; the cows thus might travel the length of the barn many times in order to collect their candy.

由于牛棚不太大,FJ 通过指定奶牛必须遵循的穿越路线来确保奶牛的乐趣。为了实现这个让奶牛在牛棚里来回穿梭的方案,FJ 在第 (i) 号隔间上张贴了一个「下一个隔间 (Next_i)」,告诉奶牛要去的下一个隔间;这样,为了收集它们的糖果,奶牛就会在牛棚里来回穿梭了。

FJ mandates that cow i should start collecting candy at stall i. A cow stops her candy collection if she arrives back at any stall she has already visited.

FJ 命令奶牛 (i) 应该从 (i) 号隔间开始收集糖果。如果一只奶牛回到某一个她已经去过的隔间,她就会停止收集糖果。

Calculate the number of unique stalls each cow visits before being forced to stop her candy collection.

在被迫停止收集糖果之前,计算一下每头奶牛要前往的隔间数(包含起点)。

输入格式

  • Line 1: A single integer: N
  • Lines 2..N+1: Line i+1 contains a single integer: next_i

输入数据第一行是一个正整数 (N)

接下来 (N) 行,一行一个正整数 (Next_i)

输出格式

  • Lines 1..N: Line i contains a single integer that is the total number of unique stalls visited by cow i before she returns to a stall she has previously visited.

输出 (N) 行,表示答案。

数据规模与约定

(1le Nle 10^5)(1le Next_ile N)

分析

题意:给定内向树森林,求出每一个节点经过足够的遍历以后会经过多少个节点。

这是一道不折不扣的树形 DP 题(没错统计树的大小也是树形 DP),只不过需要进行比较细致的分类。

建完图以后进行 DFS,记录下时间戳 (t_i),直到搜到已经搜过的点为止。接下来,会有以下几种情况需要讨论:

  1. (t<t_s),即时间戳在开始搜之前。图长这个样子:(蓝:已经搜索过;黑:本次搜索路径)

BUb6NF.png

这不就是插入别的树上吗。此时就可以直接 (ans_i=ans_j+1) 统计答案。

  1. (tge t_s),即时间戳在开始搜之后。图长这个样子:

BUbyAU.png

这不就是自♂插吗。此时还要进行进一步分类:在环上(此时时间戳-开始搜环的时间戳)和不在环上((ans_i=ans_j+1))。

所以,DFS 时要记录两个参数:初始时间戳和开始搜环的时间戳(若没有环就可以设成 (infty))。每一次回溯根据这两个参数进行分类统计答案即可。

注意事项

  • 别忘了环上和不是环上的分类。

Code

这道题还有非递归的写法,可以通过维护一个栈来进行 DP,效率更高。5ab 这里用了 DFS 法。

#include <cstdio>
#include <cctype>
#include <cstring>
using namespace std;

const int max_n = 100000;
int a[max_n], ans[max_n], vis[max_n], ord[max_n], ind = 0;

#define gc getchar
inline int read() // 快读
{
	int c = gc(), t = 1, n = 0;
	while (isspace(c)) { c = gc(); }
	if (c == '-') { t = -1, c = gc(); }
	while (isdigit(c)) { n = n * 10 + c - '0', c = gc(); }
	return n * t;
}
#undef gc

int dfs(int id, int sta) // DFS -- sta: 开始时间戳,返回值:开始搜环的时间戳
{
	int tmp; // 返回值

	vis[id] = ind++; // 时间戳

	if (vis[a[id]] == -1) // 如果下一个节点还没有被搜索过
	{
		tmp = dfs(a[id], sta); // 就开始搜索,并记录返回值

		if (vis[id] < tmp) // 当前节点不在环上
			ans[id] = ans[a[id]] + 1; // 直接统计
		else // 在环上
			ans[id] = ind - tmp; // 直接统计环的大小
	}
	else // 已经被搜过了
	{
		if (vis[a[id]] < sta) // 搜到了之前已经搜过的地方
			ans[id] = ans[a[id]] + 1, tmp = max_n + 1; // 不存在环,直接统计答案
		else // 搜到了本次搜索搜过的地方,有环
		{
			tmp = vis[a[id]]; // 就是开始搜环的地方
			ans[id] = ind - tmp; // 记录下环的大小
		}
	}

	return tmp; // 返回开始搜环的时间戳
}

int main()
{
	memset(vis, -1, sizeof(vis)); // 初始化(这个程序下标从 0 开始)

	int n = read();

	for (int i = 0; i < n; i++) // 输入
		a[i] = read() - 1;
	
	for (int i = 0; i < n; i++) // 搜索
		if (vis[i] == -1)
			dfs(i, ind);
	
	for (int i = 0; i < n; i++) // 输出答案
		printf("%d
", ans[i]);

	return 0; // 然后就 AC 了、
}
原文地址:https://www.cnblogs.com/5ab-juruo/p/solution-usaco08dec_lg2921.html