L2-038 病毒溯源(好题)

本题传送门

解题思路

本题显然使用 邻接表 \(+\) DFS

由于本题需要存路径,所以需要一个ne数组来记录路径,还需要记录每个人的父节点,因为根节点无父节点,最后要从根往下dfs。

为了最后能输出最小的排序,需要在每个节点的孩子输入完之后对其进行排序,这样就能保证每一层都是从小到大,而在所有的最长链当中,最先找到的一定就是最小的链。

本题的DFS函数是个难点

为了防止路径被更新,需要记录每个节点的最大深度,只有当发现更深的情况再更新。本题采用的DFS函数是先走到尽头,再往上更新深度。

ac✅️代码

算法一(思路不好想)

#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;

vector<int> children[10010];
int parent[10010];
int max_h[10010];
int ne[10010];
int n;

//这里的DFS函数的作用是计算每个节点的最大深度,父节点的最大深度等于子节点的最大深度 加 1。
所以先调用dfs,遍历计算出根节点的每一个子节点的最大深度,然后遍历子节点,找到最大值
void dfs(int u)
{
	max_h[u] = 1;
	ne[u] = -1;
	
	for(auto v : children[u])
	{
		dfs(v);
		if(max_h[v] + 1 > max_h[u])//主要是这里
		{
			max_h[u] = max_h[v] + 1;
			ne[u] = v;
		}
	}
}

int main()
{
	memset(parent , -1 , sizeof parent);
	cin>>n;
	for(int i = 0 ; i < n ; i++)			
	{
		int t;cin>>t;
		for(int j = 0 ; j < t ; j ++)
		{
			int x;cin>>x;
			parent[x] = i;
			children[i].push_back(x);	
		}
		sort(children[i].begin() , children[i].end());
        //
	}
	
	int root ;
		
	for(int i = 0 ; i < n ; i++)
	{
		if(parent[i] == -1 ) root = i;
	}
	
	dfs(root);

	cout<<max_h[root]<<endl;
	
	cout<<root;
	
	while(ne[root] != -1)
	{
		cout<<" "<<ne[root];
		root = ne[root];
	}
	
	return 0;
	
}

算法一的改良版(比较好理解)

#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>

using namespace std;

vector<int> children[100010];
int parent[100010];
int max_h[100010];
int ne[100010];
int n;

int dfs(int u)
{
    if (max_h[u] != 0) 
    {
        return max_h[u];
    }

    if (children[u].empty())
    {
        ne[u] = -1;
        return max_h[u] = 1;
    }

    int current_max = 1;
    int best_child = -1;

    for (int v : children[u])
    {
        int child_h = dfs(v);
        int path_through_v = child_h + 1;

        // 决策:只有大于才更新,配合前面的 sort 保证了字典序最小
        if (path_through_v > current_max)
        {
            current_max = path_through_v;
            best_child = v;
        }
    }

    ne[u] = best_child;
    max_h[u] = current_max;
    return max_h[u]
}

int main()
{
    memset(parent, -1, sizeof parent);
    memset(max_h, 0, sizeof max_h);

    if (!(cin >> n)) return 0;

    for (int i = 0; i < n; i++)
    {
        int k;
        cin >> k;
        for (int j = 0; j < k; j++)
        {
            int x;
            cin >> x;
            parent[x] = i;
            children[i].push_back(x);
        }
        sort(children[i].begin(), children[i].end());
    }

    int root = 0;
    for (int i = 0; i < n; i++)
    {
        if (parent[i] == -1)
        {
            root = i;
            break;
        }
    }

    dfs(root);

    cout << max_h[root] << endl;

    cout << root;
    int curr = root;
    while (ne[curr] != -1)
    {
        curr = ne[curr];
        cout << " " << curr;
    }
    cout << endl;

    return 0;
}

算法二(简单很多,非常直观 , 但是很容易被卡掉)

  • 这种代码被卡,与 CF 2185D题一样,我刚开始是用两个vector数组,然后用其中一个直接赋值给
    另一个,思路是对的,但是超时了,甚至它的优化方法与本题也是几乎一样

天梯赛的数据还是太友好了

#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>

using namespace std;

vector<int> temp_path;
vector<int> best_path;
vector<int> children[10010];
int parent[10010];

void dfs(int u)
{
	temp_path.push_back(u);
	
	if(!children[u].size())
	{
		if(temp_path.size() > best_path.size())
		{
			best_path = temp_path;
		}
	}
	else//这里的else不要也行
	{
		for(int v : children[u])
		{
			dfs(v);//由于这里修改是全局数组,需要回溯
		}
	}
	
	temp_path.pop_back();
}

int main()
{
	memset(parent,-1,sizeof parent);
	
	int n;cin>>n;
	
	for(int i = 0 ; i < n ;i ++)
	{
		int x;cin>>x;
		for(int j = 0 ; j < x ; j ++)
		{
			int y ; cin>>y;
			parent[y] = i;
			children[i].push_back(y);
		}
		sort(children[i].begin() , children[i].end());
	}
	
	int root = 0 ;
	
	for(int i = 0 ; i < n ; i++) if(parent[i] == -1) root = i;

	
	dfs(root);
	
	cout<<best_path.size()<<endl;
	
	
	for(int i = 0 ; i < best_path.size() ; i ++)
	{
		cout<<best_path[i];
		if(i != best_path.size() - 1) cout<<" ";
	}
	return 0;
}
posted @ 2026-03-17 10:21  shuiwangrenjia  阅读(36)  评论(0)    收藏  举报