【题解】食物链 By 5ab as a juruo

题目

题目来源:CCF HAOI2016;

评测地址:LOJ#2060LG3183

题目描述

辣鸡 LOJ,图床又炸了

如图所示为某生态系统的食物网示意图,据图回答第 (1) 小题。

现在给你 (n) 个物种和 (m) 条能量流动关系,求其中的食物链条数。物种的名称为从 (1)(n) 编号。

(m) 条能量流动关系形如:

[a_1quad b_1 ]

[a_2quad b_2 ]

[a_3quad b_3 ]

[cdots ]

[a_{m-1}quad b_{m-1} ]

[a_mquad b_m ]

其中 (a_i)(b_i) 表示能量从物种 (a_i) 流向物种 (b_i)注意单独的一种孤立生物不算一条食物链

输入格式

第一行两个整数 (n)(m)

接下来 (m) 行每行两个整数 (a_i)(b_i) 描述 (m) 条能量流动关系。

输出格式

一个整数即食物网中的食物链条数。

数据规模及约定

(1le nle 10^5)(0le mle 2 imes 10^5)

保证输入数据符合生物学特点,且不会有重复的能量流动关系出现。题目保证答案不会爆 int。

分析

题意是给你一张 DAG,求出满足起点入度为 (0),终点出度为 (0) 的路径的数量。

这显然是一个很简单的搜索,最简单的方法就是从每一个入度为 (0) 的点进行 DFS 并统计。但是这并不一定可以通过。

注意到这是一个 DAG,所以我们进行记忆化。记录 (f_i) 为第 (i) 个点作为起点,终点出度为 (0) 的路径数量。

那么,对于一个节点,(f_i) 的值就是所有其后继节点的 (f_j) 的和,如果有一个后继节点没有后继节点,那么就对原节点 (+1),创建一条新的路径。

相比来说,这个算法的优点在于每一个节点只处理一次,复杂度是 (O(n+m)),绰绰有余。

Code

一定要记住,一个节点并不能算作一条路径,在代码中要体现这一点。

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

const int max_n = 100000, max_m = 200000; // 常量

int hd[max_n], des[max_m], nxt[max_m], ind[max_n] = {}, e_cnt = 0; // 邻接表存图和入度统计
bool vis[max_n] = {}; // 是否处理过
long long dp[max_n] = {}, ans = 0; // 记忆化

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

void add_edge(int s, int t) 
{
	des[e_cnt] = t;
	nxt[e_cnt] = hd[s];
	hd[s] = e_cnt++;
}

void dfs(int id) // 搜索
{
	vis[id] = true; // 标记

	if (hd[id] == -1) // 出度为0
	{
		dp[id] = -666; // 标记
		return;
	}
	
	for (int p = hd[id]; p != -1; p = nxt[p]) // 遍历后继节点
	{
		if (!vis[des[p]]) // 处理
			dfs(des[p]);
		
		if (dp[des[p]] == -666) // 有标记
			dp[id]++; // 新建一条路径
		else
			dp[id] += dp[des[p]]; // 否则累加
	}
}

int main()
{
	memset(hd, -1, sizeof(hd)); // 初始化

	int n = read(), m = read(), ta, tb;

	for (int i = 0; i < m; i++) // 输入
	{
		ta = read(), tb = read();

		add_edge(ta - 1, tb - 1); // 建图
		ind[tb-1]++; // 统计入度
	}

	for (int i = 0; i < n; i++)
		if (!ind[i]) // 入度为0
		{
			dfs(i); // 计算
			
			if (dp[i] != -666) // 不是单独节点
				ans += dp[i]; // 统计
		}

	printf("%lld
", ans); // 精准结束

	return 0;
}

后记

这道题的一个重点就是一个点不能作为一条路径,这一点当时卡了 5ab 好久,后来在看题解时才恍然大悟。

做题时,注意题目中的关键是很重要的,这可能就是拉分的关键。

原文地址:https://www.cnblogs.com/5ab-juruo/p/solution-haoi2016_lg3183_loj2060.html