传染链 (25 分)

代码长度限制 16 KB
时间限制 400 ms
内存限制 64 MB

题目描述

某病毒可以人传人,且传染能力极强,只要与已感染该病毒的人发生接触即刻感染。
现给定一些感染该病毒的人员接触关系,要求你找出其中最早最长的一条传染链。

输入格式

输入在第一行中给出一个正整数 N(N≤104),即感染病毒的人员总数,从 0 到 N−1 进行编号。

随后N 行按照编号顺序给出人员接触信息,每行按以下格式描述该人员的接触对象:

k 接触人员1 …… 接触人员k

其中 k 是该编号人员接触的其他人总数,后面按照时间先后给出所有接触的人员编号。题目保证传染源头有且仅有一个,且已被感染人员不会与另一个感染人员再接触。

输出格式

第一行输出从源头开始的最早最长传染链长度

第二行输出从源头开始的最早最长传染链,编号之间以1个空格分隔,行首尾不得有多余空格。这里的最早最长传染链是指从源头开始的传染链上的人数最多,且被感染的时间最早

所谓时间最早指的两个长度相等的传染链{a1,a2,…,an}和{b1,b2,…,bn},存在1≤k<n,对于所有的i (1≤i<k)都满足ai=bi,且ak被感染的时间早于bk被感染的时间。

输入样例

10
0
3 3 4 7
2 1 9
1 6
1 5
0
0
0
2 6 0
1 8

输出样例

4
2 1 3 6

个人思路

题意分析

题目要求是从给定的传染关系中找出最早最长的传染链,先针对输入输出样例分析,有十条传染关系,人员0没有接触其他人,人员1先后接触了三个人,分别是3、4、7,人员2先后接触了两个人,分别是1、9……

因为不知道源头是谁,所以可以依次假设每个人是源头去找,最长最早的那条链一定是从源头开始。用输出结果做示例,当然也可以用其他的,寻找过程模拟如下
在这里插入图片描述

由上图可以看出最长的传染链有4条,然后分析最早的传染链,因为输入有先后顺序,也就是说2号是先传染的1号,然后才传染的9号,由此可以得知最早最长的链就是最上方的2->1->3->6 。

思路分析

接下来把这个演化过程抽象为代码的逻辑,首先我们的寻找过程是逐个寻找的,也就是从0号开始一直往下找,即一个遍历的过程。

针对每一个源头,我们需要按照先后顺序进行寻找,还以上图为例,因为要找最长的链,所以才有深度优先搜索的思维来找,2号首先接触的人员是1号,然后1号首先接触的人员是3号,3号接触了6号,6号没有接触其他人,到此最深也就到了6号没有再继续传染,一条链就找完了。这是记录下这条链和这条链的长度4,后面有更长的传染链则直接把记录的这条覆盖掉,相等长度则不覆盖,因为后面长度为4的链一定晚于这条链。

等待遍历结束就可以得到最早最长链即它的长度。

具体实现思路

由上便可以得到几个代码实现上的思路

  1. 要求找最长的路径,考虑用dfs
  2. 因为每个人感染的人数不同,所以采用动态数组会比较方便,当然也可以用数组来表示,只需要给不需要的空间一个如(-1)这样的标识即可。
  3. 将所有的动态数组存放在一个数组里面,这样通过下标就可以访问每一个人先后接触的有哪些人
  4. 在dfs里面也可以用动态规划的思想来对时间进行优化,用一个step[] 数组记录从某一个位置开始能走的最大步数,后面出现这个数字时便可直接调用,避免重复计算。比如在上面图中的案例中,一开始已经得知了从3开始最深是2,那后面如果又走到了3,如果已经记录下来了,那直接便可以加上2,而不用重复走一遍3->6
  5. 用一个nxt数组找出从某一个位置开始找到可以走的最大的步数时进行记录,这样就可以直接对最长路径进行输出,和上一条思路一致,不再赘述,都是为了避免重复计算
#include<bits/stdc++.h>
using namespace std;

vector<int> v[10010];//记录人员接触信息

int step[10010];//step[i]表示从人员i开始可以传染的最长的链的长度
int nxt[10010];//最终的nxt[i]将表示最长最早的传染链上人员i传染的下一个人员
int ans=0;//最终的传染链长度

//获取从人员index开始的最长最早的传染链长度
int dfs(int index)
{
    
    
    //人员index没有接触其他人时,从它开始的传染链则为他自己,长度即为1
    if(v[index].size()==0)
        return 1;
        
    int steps=0;//将作为返回值,表示从index开始的最早最长的传染链长度
    for(int i=0;i<v[index].size();i++)//遍历人员index接触过的人
    {
    
    
        //v[index][i]指人员index接触的第i个人的编号,假设它为k
        int k=v[index][i];
        //step[k]即表示从人员k开始的最早最长的传染链长度
        //如果这个长度加上它走到k这一步大于现在记录的最长的长度steps,则进行更新,step[k]等于0时前面的大于情况也满足,需要排除
        if(step[k]+1>steps&&step[k]!=0)
         {
    
    
            //从index开始走到人员k只需要走1步,
            //从人员k开始的最长的传染链长度为step[k],
            //所以从index开始最长的传染链长度就为step[k]+1
            steps=step[k]+1;
            nxt[index]=k;//目前找到的最长最早传染链中index接触的下一个人为k
        }
        else if(step[k]==0)//如果还不知道从k开始的传染链长度
        {
    
    
            //获取从k开始的最长的传染链长度dfs(k)
            //然后加上从index走到k这个位置的1步
            int temp=dfs(k)+1;
            if(temp>steps)//和上一个if的判断方法一致
            {
    
    
                steps=temp;
                nxt[index]=k;
            }
        }
    }
    step[index]=steps;//记录从index开始最早最长的传染链的长度
    return steps;
}
int main()
{
    
    
    int n;
    cin>>n;//总人数
    memset(v,0,sizeof(v));
    memset(step,0,sizeof(step));
    memset(nxt,0,sizeof(nxt));
    for(int i=0;i<n;i++)//存储所有的传染信息
    {
    
    
        int k;
        cin>>k;//人员i接触的人数
        for(int j=0;j<k;j++)//把接触到的每个人都存入动态数组
        {
    
    
            int t;
            cin>>t;
            v[i].push_back(t);
        }
    }
    int index=0;
    for(int i=0;i<n;i++)//遍历每一条人员接触信息
    {
    
    
        int temp=dfs(i);//从人员i开始的最长的那条传染链的长度
        //传染链长度大于上一次记录的结果是记录该条链的起始点并更新最大长度
        if(temp>ans)
        {
    
    
            index=i;//起始点
            ans=temp;
        }
    }
    cout<<ans<<endl;
    for(int i=0;i<ans-1;i++)
    {
    
    
        cout<<index<<" ";
        index=nxt[index];
    }
    cout<<index;
}

猜你喜欢

转载自blog.csdn.net/weixin_46050495/article/details/124134007
今日推荐