【AcWing】蓝桥杯集训每日一题Day23|树形DP|字典序最小3465.病毒溯源(C++)

3465.病毒溯源
3465. 病毒溯源 - AcWing题库
难度:中等
时/空限制:0.4s / 64MB
总通过数:2108
总尝试数:4379
来源: CCCC天梯赛L2-038
算法标签 树形DP求方案

题目内容

病毒容易发生变异。

某种病毒可以通过突变产生若干变异的毒株,而这些变异的病毒又可能被诱发突变产生第二代变异,如此继续不断变化。

现给定一些病毒之间的变异关系,要求你找出其中最长的一条变异链。

在此假设给出的变异都是由突变引起的,不考虑复杂的基因重组变异问题 ------ 即每一种病毒都是由唯一的一种病毒突变而来,并且不存在循环变异的情况。

输入格式

输入在第一行中给出一个正整数 N,即病毒种类的总数。于是我们将所有病毒从 0 到 N−1 进行编号。

随后 N 行,每行按以下格式描述一种病毒的变异情况:
k 变异株1 ...... 变异株k

其中 k 是该病毒产生的变异毒株的种类数,后面跟着每种变异株的编号。第 i 行对应编号为 i 的病毒(0≤i<N)。题目保证病毒源头有且仅有一个。

输出格式

首先输出从源头开始最长变异链的长度。

在第二行中输出从源头开始最长的一条变异链,编号间以 1 个空格分隔,行首尾不得有多余空格。如果最长链不唯一,则输出最小序列。

注:我们称序列 {a1,...,an} 比序列 {b1,...,bn} "小",如果存在 1≤k≤n 满足 ai=bi 对所有 i<k 成立,且 ak<bk。

数据范围

1≤N≤10^4

输入样例:
10
3 6 4 8
0
0
0
2 5 9
0
1 7
1 2
0
2 3 1
输出样例:
4
0 4 9 1
题目解析

一开始是一个病毒,有可能会变异成多个不同的病毒,变异的病毒还可能变异成新的病毒

保证了变异的方式是一颗树,不会发生交叉变异和循环变异,一定是从父节点向子节点变异

在整个树中从根节点开始的最长的一条路径,并且把路径输出

如果有多个方案的话,输出字典序最小的方案

数据范围是10000

时间复杂度只需要在 O ( n log ⁡ n ) O(n\log n) O(nlogn)

要分成两步来看
怎么去求最大值

可以看成递归问题或简单的树形DP问题

考虑某一个局部,从当前节点开始,如果往下搜的话,最长的链有多长

比如当前节点有三个儿子的话,从当前节点往下走有三条路径,应该选哪条路径走呢

因为这个题要求最长的路径,要从三个儿子中选择一条能往下走的最长的路径

如果当前节点是u,三个儿子是abc的话

f[u]来表示从u往下走的最长路径,f[a],f[b],f[c]分别表示从abc往下走的最长路径
f [ u ] = m a x ( f [ a ] , f [ b ] , f [ c ] ) + 1 f[u] = max(f[a], f[b], f[c])+1 f[u]=max(f[a],f[b],f[c])+1

所以这个题就是非常简单的树形DP

只要按照这个递推方程,递归地求一遍就可以了,遍历一下整棵子树就可以了

如何去求方案,以及如何求字典序最小的方案

根节点是root

每次要知道,根节点要取最长路径的话,应该是往哪个儿子走

  1. 当然可以后面动态计算
    最后求方案的时候可以再递归一遍
    递归的时候,判断一下f[root]是从哪个儿子转移过来的,判断一下哪个儿子取了最大值就可以了
  2. 记忆化存储
    转移的时候,对u来说,记录一下u是从哪个儿子转移过来的,如果最大值是f[a]的话,就把son[u]记成a
    存下来之后,第一个点是root,第二个点就是son[root],接下来的点就是每次套一个son就可以了
    只要在转移的时候求一下son数组,就可以把整个路径找出来了
如何找字典序最小的方案

在求f[u]的时候,这三个链当中,只有唯一的一个最长

因为要保证最长,所以方案是唯一的

如果有两个儿子都可以取到最长

f[a] = f[b] > f[c]

这时如何选a和b

由于要字典序最小,就看一下a和b哪个编号更小,取编号更小的那一个就可以了

输出方案可以用DP

递归求完每个状态之后,再从根节点再递归一遍就可以了

或者动态判断一下

没有告诉根节点,需要判断一下根节点是谁,看一下哪个点没有父节点

用bool数组标记

代码
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 10010;

int n;
int h[N], e[N], ne[N], idx;
int son[N];
bool st[N]; //标记有没有父节点

//加边函数模板
void add(int a, int b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

int dfs(int u)
{
	int res = 0;
	//当前点如果没有儿子的话,标记成-1
	son[u] = -1;

	//枚举一下当前点的所有子节点
	//~i等价于i != -1
	for (int i = h[u]; ~i; i = ne[i])
	{
		//用j表示当前子节点的编号
		int j = e[i];
		//递归求一下从j往后走的最大长度
		int d = dfs(j);
		//如果距离更长 或者 距离相等而且编号更小的话
		if (d > res || d == res && j < son[u])
		{
			res = d;
			son[u] = j;
		}
	}

	//最后返回最长路径+自己
	return res + 1;
}

int main()
{
	scanf("%d", &n);
	//先把路径表表头清空
	memset(h, -1, sizeof h);

	//读入每个点的所有儿子
	for (int i = 0; i < n; i ++)
	{
		//先读入每一个点儿子的数量
		int cnt;
		scanf("%d", &cnt);
		//依次读入所有的儿子
		while (cnt --)
		{
			int x;
			scanf("%d", &x);
			//添加一条边
			add(i, x);
			//i的儿子是x,所以x有父节点
			st[x] = true;
		}
	}

	//编号从0开始
	int root = 0;
	//找根节点
	while (st[root]) root ++;

	//递归求一下,从根节点开始的最大的路径长度
	int d = dfs(root);

	printf("%d\n", d);

	//行内没有空格
	printf("%d", root);

	for (int cur = son[root]; cur != -1; cur = son[cur])
		printf(" %d", cur);

	return 0;
}
相关推荐
StrokeAce44 分钟前
linux桌面软件(wps)内嵌到主窗口后的关闭问题
linux·c++·qt·wps·窗口内嵌
家有狸花4 小时前
VSCODE驯服日记(三):配置C++环境
c++·ide·vscode
dengqingrui1234 小时前
【树形DP】AT_dp_p Independent Set 题解
c++·学习·算法·深度优先·图论·dp
C++忠实粉丝4 小时前
前缀和(8)_矩阵区域和
数据结构·c++·线性代数·算法·矩阵
ZZZ_O^O5 小时前
二分查找算法——寻找旋转排序数组中的最小值&点名
数据结构·c++·学习·算法·二叉树
小飞猪Jay7 小时前
C++面试速通宝典——13
jvm·c++·面试
rjszcb8 小时前
一文说完c++全部基础知识,IO流(二)
c++
小字节,大梦想9 小时前
【C++】二叉搜索树
数据结构·c++
吾名招财9 小时前
yolov5-7.0模型DNN加载函数及参数详解(重要)
c++·人工智能·yolo·dnn
我是哈哈hh9 小时前
专题十_穷举vs暴搜vs深搜vs回溯vs剪枝_二叉树的深度优先搜索_算法专题详细总结
服务器·数据结构·c++·算法·机器学习·深度优先·剪枝