TZOJ 6712 短视频 Tarjan+缩点+dfs
本题可以用bfs或spfa暴力水过去,但是正解可以用tarjan+缩点来实现。
题目:
描述:
最近,刷短视频逐渐在老年人之间流行,但老年人比较健忘,他们收到朋友发来的视频后都会转发给自己所有的朋友,这导致了有些人会反反复复收到同一个视频。
现在给定n个老年人(1~n编号),以及各自的朋友圈,有一条短视频从某个人s发出,问哪些人会反复收到这条视频。
输入:
第一行有两个正整数n和s(n<=50, 1<=s<=n),表示总共有n个老年人,短视频从s发出。
第二行为n个空格隔开的字符串(长度小于20的字母组成),表示n人的姓名。
接下来有n行,第i行表示编号为i人的朋友列表,每行的第一个数为m,表示i共有m个朋友,后面是m个朋友的编号(自己不会是自己的朋友)。
输出:
输出所有可能会反反复复收到这条短视频的人的姓名,各个姓名之间一个空格隔开,按照姓名出现的先后顺序输出。
如果没有人反反复复收到,则输出OK
样例:
输入:
6 3
crq Carter tzcer wad hyl hxf
2 3 5
0
1 4
1 1
1 2
2 5 4
输出:
crq Carter tzcer wad hyl
思路:
题意有点不好理解,一开始以为是一个视频看两遍就可以算是反复看。后来才知道是一个视频必须看无穷次。
那么就可以理解为当短视频传到某个强连通分量时,与这个强连通分量相连的所有点,包括这个强联通分量都可以看无穷次。
如图所示:
从根节点出发可以经过两个scc;
而这两个scc所连接的点就都可以看无数次。
这样。我们可以做tarjan缩点处理。
在对起始点进行dfs遍历。
下面展示一些 ac代码:
#include<bits/stdc++.h>
using namespace std;
string name[55];
vector<int>G[100];
vector<int>P[100];
int instack[100],dfn[100],low[100],scc[100],sum[100],tot,number,ans;
bool vis[100];
stack<int>st;
void tarjan(int x)//做一次tarjan
{
dfn[x]=low[x]=++tot;
st.push(x);
instack[x]=1;
for(int i=0;i<G[x].size();i++)
{
int u=G[x][i];
if(!dfn[u])
{
tarjan(u);
low[x]=min(low[x],low[u]);
}
else if(instack[u])
{
low[x]=min(low[x],dfn[u]);
}
}
if(dfn[x]==low[x])
{
int t;
number++;
do
{
t=st.top();st.pop();
instack[t]=0;
scc[t]=number;
sum[number]++;//统计scc中的点个数。
}while(t!=x);
}
}
void dfs(int x,bool flag)//对新图跑一边dfs
{
//cout<<" "<<x<<endl;
if(sum[x]>1)//如果scc中的点个数大于1,那么后面的点就都可以看无数遍
{
flag=true;
vis[x]=true;//将该scc设置为访问过的节点
ans++;
}
for(int i=0;i<P[x].size();i++)//遍历后续节点
{
int to=P[x][i];
vis[to]=flag;
dfs(to,flag);
}
}
int main()
{
ios::sync_with_stdio(false);
int n,s;
cin>>n>>s;
for(int i=1;i<=n;i++)
{
cin>>name[i];
}
for(int i=1;i<=n;i++)
{
int k;
cin>>k;
while(k--)
{
int p;
cin>>p;
G[i].push_back(p);
//G[p].push_back(i);
}
}
for(int i=1;i<=n;i++)
{
if(!dfn[i])
{
tarjan(i);
}
}
for(int i=1;i<=n;i++)
{
//cout<<scc[i]<<endl;
}
for(int i=1;i<=n;i++)//缩点建新图。
{
for(int j=0;j<G[i].size();j++)
{
int u=G[i][j];
if(scc[u]!=scc[i])
{
P[scc[i]].push_back(scc[u]);
}
}
}
dfs(scc[s],false);
//cout<<ans<<endl;
if(ans)
{
int num=0;
for(int i=1;i<=n;i++)
{
if(vis[scc[i]])//如果该点的强连通被标记就说明看了无数遍短视频。
{
if(num)
{
cout<<" "<<name[i];
}
else
{
cout<<name[i];num=1;
}
}
}
cout<<endl;
}
else cout<<"OK"<<endl;
}