L2-038 病毒溯源 (25 分)
病毒容易发生变异。某种病毒可以通过突变产生若干变异的毒株,而这些变异的病毒又可能被诱发突变产生第二代变异,如此继续不断变化。
现给定一些病毒之间的变异关系,要求你找出其中最长的一条变异链。
在此假设给出的变异都是由突变引起的,不考虑复杂的基因重组变异问题 —— 即每一种病毒都是由唯一的一种病毒突变而来,并且不存在循环变异的情况。
输入格式:
输入在第一行中给出一个正整数 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
本题较难,一看就是用并查集或dfs。题意:病毒代代相传,源头只有一个(祖先就一个),求最长链。当时比赛时感觉会用dfs,但时间问题感觉写不出来,就用了并查集,得到20分(没有加入同长度链的比较)。后来用并查集加入了同长度链比较,结果只得了16分。。。多了一个内存超限错误。下面上代码:
20分:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll nl=1e5+5;
ll p[nl];
ll b[nl]={0};
ll c[nl];
vector<ll>v[nl];
void init(){
for(ll i=0;i<nl;i++){
p[i]=i;
}
}
ll num=1;
ll find(ll x){
while(x!=p[x]){
num++;
x=p[x];
}
return num;
}
int main(){
init();
ll n;
cin>>n;
ll i,j;
for(i=0;i<n;i++){
ll k;
cin>>k;
while(k--){
ll x;
cin>>x;
p[x]=i;
b[x]=1;
}
}
ll a=0;
ll b;
for(i=0;i<n;i++){
num=1;//初始化num,不然find一下就改变了
if(a<find(i)){
num=1;//同理,不可缺少
a=find(i);
b=i;
}
}
cout<<a<<endl;
//cout<<b<<endl;
i=0;
while(b!=p[b]){//这里并没有进行同链长的排序
c[i]=b;
b=p[b];
i++;
}
c[i]=b;//别忘记记录b==p[b]时的值。
//cout<<i<<endl;
cout<<c[i];
for(j=i-1;j>=0;j--){
cout<<" "<<c[j];
}
}
16分代码:多了内存超限的错误
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll nl=1e5+5;
ll p[nl];
ll b[nl]={0};
ll c[nl];
vector<ll>v[nl];
void init(){
for(ll i=0;i<nl;i++){
p[i]=i;
}
}
ll num=1;
ll i,j;
ll find(ll x){
while(x!=p[x]){
v[i].push_back(x);
num++;
x=p[x];
}
v[i].push_back(x);
return num;
}
bool cmp(vector<ll>a,vector<ll>b){//进行了同链长的排序
if(a.size()!=b.size()){
return a.size()>b.size();
}else if(a!=b){
return a<b;
}
}
int main(){
init();
ll n;
cin>>n;
//ll i,j;
for(i=0;i<n;i++){
ll k;
cin>>k;
while(k--){
ll x;
cin>>x;
p[x]=i;
b[x]=1;
}
}
ll a=0;
ll b;
for(i=0;i<n;i++){
//v[i].push_back(i);
//cout<<find(i)<<endl;
num=1;
if(a<find(i)){
num=1;
a=find(i);
b=i;
}
}
cout<<a<<endl;
sort(v,v+n,cmp);
cout<<v[0][a-1];
for(i=a-2;i>=0;i--){
cout<<" "<<v[0][i];
}
}
25分代码:dfs代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll nl=1e4+5;
ll a[nl]={0};//用于查找源点
ll b[nl][nl]={0};//记录两病毒之间是否有亲属关系
vector<ll>v,ve;//用于记录最长链,vector的好用之处,它可以直接对序列进行比较***
ll n,maxn;//maxn即是最长距离
void dfs(ll x,ll y){
if(y>maxn){
maxn=y;
ve=v;
}else if(y==maxn){
if(v<ve){//真的好用,省去很多代码
ve=v;
}
}
for(ll i=0;i<n;i++){
if(b[x][i]==1){
v.push_back(i);//插入一个新的点
dfs(i,y+1);//让新的序列去递归
v.pop_back(); //在删去此点,因为病毒可以衍生多个不同的新病毒为其他点腾地方
}
}
}
int main(){
cin>>n;
ll i,j;
for(i=0;i<n;i++){
ll k;
cin>>k;
while(k--){
ll x;
cin>>x;
a[x]=1;
b[i][x]=1;
}
}
ll u=0;
for(i=0;i<n;i++){
if(a[i]==0){
u=i;//找到源点
break;
}
}
v.push_back(u);//初始化v
dfs(u,1);
cout<<ve.size()<<endl;
for(i=0;i<ve.size();i++){
if(i==0){
cout<<ve[0];
}else{
cout<<" "<<ve[i];
}
}
}