团体天梯练习 L2-038 病毒溯源

发布时间 2023-04-20 11:39:53作者: Amαdeus

L2-038 病毒溯源

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

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

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

输入格式:

输入在第一行中给出一个正整数 \(N\)\(≤10^{4}\) ),即病毒种类的总数。于是我们将所有病毒从 \(0\)\(N−1\) 进行编号。

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

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

输出格式:

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

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

注:我们称序列 { \(a_{1}\) , ⋯ , \(a_{n}\) } 比序列 { \(b_{1}\) , ⋯ , \(b_{n}\) } “小”,如果存在 \(1≤k≤n\) 满足 \(a_{i}\) = \(b_{i}\) 对所有 \(i<k\) 成立,且 \(a_{k}\) < \(b_{k}\)

输入样例:

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


解题思路

题目大致意思就是要我们求出一颗上从根节点到叶子节点的最长长度的序列,并且这个序列的字典序尽可能小。所以我们用 \(BFS\) 来解决这个问题,而关键在于字典序最小和答案的还原。

当我们在进行 \(BFS\) 前,只需要保证每个点的孩子节点的顺序是递增的,就可以保证字典序最小了。假设当前出队的点为 \(u\),到达 \(u\) 的距离为 \(d\),其孩子节点为 \(v_{1}, v_{2} ... v_{k}\),很明显到达这些孩子节点的距离为 \(d + 1\),由于孩子节点序列是升序的,第一个入队的节点一定能够保证当前答案序列字典序最小,并且之后的孩子节点 \(v_{2} ... v_{k}\) 不会影响到答案。所以可以在建图的时候,对孩子节点序列进行排序。

一开始,还需要找到树中的根节点 \(root\),只需要定义一个 \(fa\) 数组,\(fa[v]\) 用来表示 \(v\) 的父节点,如果一个节点没有父节点,其 \(fa\) 等于 \(-1\) (初始情况下全部为 \(-1\))。用一个循环找到 \(fa\)\(-1\) 的节点,其就是整棵树的根节点。这个 \(fa\) 还有另一个用处,那就是还原答案,我们可以记录答案序列的最后一个节点编号,然后在得到答案序列时,逆序迭代直到根节点,此时得到的是答案序列的逆序,之后翻转一下或者逆序输出即可。

/*   一切都是命运石之门的选择  El Psy Kongroo  */
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<cmath>
#include<functional>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<int, pii> piii;
typedef pair<double, double> pdd;
typedef pair<string, int> psi;
//typedef __int128 int128;
#define PI acos(-1.0)
#define x first
#define y second
//int dx[4] = {1, -1, 0, 0};
//int dy[4] = {0, 0, 1, -1};
const int inf = 0x3f3f3f3f, mod = 1e9 + 7;


const int N = 1e4 + 10;
vector<int> e[N];
int fa[N], root;
int n, res, ed;

void bfs(){
    queue<pii> q;
    q.push({root, 1});

    while(!q.empty()){
        auto [u, d] = q.front();
        q.pop();

        if(d > res){    //只有在大于时更新答案
            res = d;    
            ed = u;     //记录最后一个点
        }

        for(auto &v : e[u]) q.push({v, d + 1});
    }
}

void show(){
    cout << res << endl;
    vector<int> ans;
    int cur = ed;
    while(cur != root){
        ans.push_back(cur);
        cur = fa[cur];
    }
    ans.push_back(root);

    reverse(ans.begin(), ans.end());
    for(int i = 0; i < (int)ans.size() - 1; i ++ ) cout << ans[i] << ' ';
    cout << ans.back() << endl;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n;
    memset(fa, -1, sizeof(fa));
    for(int u = 0; u < n; u ++ ){
        int k; cin >> k;
        while(k -- ){
            int v; cin >> v;
            e[u].push_back(v);
            fa[v] = u;
        }
        sort(e[u].begin(), e[u].end()); //字典序最小
    }

    while(~fa[root]) root ++ ;

    bfs();

    show();

    return 0;
}