https://www.luogu.com.cn/problem/P2746
该题简化一下就是下面两个问题:
1、最少要给多少个点发消息,才能使得所有的点都收到消息(消息可以随边传递)
2、最少需要多少条边才能使得图变成强连通图
首先我们先使用tarjan算法求出图中的强连通分量,对于一个强连通分量,可以当做一个点来考虑,所以我们可以缩点,然后得到DAG图。
那么对于第一个问,即是入度为0的点有多少个,因为入度为0的点无法收到消息。
对于第二问,只要加max(s1,s2)条边,就能使得DAG变成强连通图, s1表示入度为0的点的个数,s2表示出度为0的点的个数。设s1 > s2, 那么首先加s2条边,这s2条边连接的是入度为0和出度为0的点,然后剩下s1-s2个入度为0的点, 那么随便加s1-s2条边即可。
当然有一个特殊情况需要特判一下,当整个图原本就是一个强连通图时,缩完点之后,s1,s2都是1,但是本身不需要加边就是一个强连通图。``
AC代码:
#include<iostream>
#include<cstdio>
#include<stack>
#include<cstring>
using namespace std;
int T;
int head[110],a[10010],b[10010],dfn[110],low[110],scc[110],rd[110],cd[110],f[110][110];
bool vis[110];
struct edge{
int to,w,next;
}e[10010];
int cnt;
void add(int u,int v,int w){
e[++cnt].to =v;
e[cnt].w =w;
e[cnt].next =head[u];
head[u]=cnt;
}
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
int tot,scnt=0;
stack<int>s;
void tarjan(int u){
vis[u]=1;
s.push(u);
dfn[u]=low[u]=++tot;
for(int i=head[u];i;i=e[i].next ){
int v=e[i].to ;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
scnt++;
int x;
do
{
x=s.top();s.pop();
vis[x]=0;
scc[x]=scnt;
}while(x!=u);
}
}
int main(){
int n;
cin>>n;
int m=0;
for(int u=1;u<=n;u++){
int v;
while(cin>>v&&v>0){
m++;
a[m]=u,b[m]=v;
add(u,v,1);
}
}
for(int i=1;i<=n;i++){
if(!dfn[i])tarjan(i);
}
int ansa=0;
int ansb=0;
int sum=0;
if(scnt==1){
ansa=1,ansb=0;
}
else{
for(int i=1;i<=m;i++){
if(scc[a[i]]!=scc[b[i]]&&!f[scc[a[i]]][scc[b[i]]]){
cd[scc[a[i]]]++;
rd[scc[b[i]]]++;
f[scc[a[i]]][scc[b[i]]]=1;
}
}
for(int i=1;i<=scnt;i++){
if(rd[i]==0)ansa++;
if(cd[i]==0)sum++;
}
ansb=max(ansa,sum);
}
cout<<ansa<<endl;
cout<<ansb<<endl;
return 0;
}