NewCoder_13_E 通知小弟[缩点]
链接:https://www.nowcoder.net/acm/contest/76/E
来源:牛客网
通知小弟
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 32768K,其他语言65536K
64bit IO Format: %lld题目描述
在战争时期,A国派出了许多间谍到其他国家去收集情报。因为间谍需要隐秘自己的身份,所以他们之间只是单向联系。所以,某个间谍只能单向联系到一部分的间谍。同时,间谍也不知道跟他联系的是谁。
HA是间谍们的老大,但他也只能联系到部分的间谍。HA现在有一项命令有告诉所有的间谍。HA想要知道他至少要告诉多少个他能联系上的间谍才能通知到所有的间谍。
输入描述:
有多个测试数据。
对于每个测试数据:
第一行为一个整数n,m(0 < n,m<=500)
代表间谍的数量和HA能通知到的间谍的
数量(间谍的编号为1-n);
第二行为m个用空格隔开的整数xi,代表HA能通知到的间谍的编号;
第三行到第n+2行,每一行第一个整数ai(0 < =ai < n)表示第i-2个间谍能单向联系到的间谍数。之后有ai个用空格隔开的整数,表示间谍i-2能单向联系到的间谍的编号。
输出描述:
输出一行,此行中有一个整数,代表HA至少需要联系的间谍数。如果HA不能通知到所有间谍,输出-1。
示例1
输入
3 2
1 2
1 2
1 1
0
输出
-1
示例2
输入
3 1
1
2 2 3
0
0
输出
1
有向图缩点成DAG(有向无环图)的水题。用到的算法是tanjan求有向图的强连通分量。
一、
题目的意思是 小弟 之间构成一个有向图,然后BOSS 可以直接通知给定的几个小弟。
问最少通知几个小弟,使得所有人被间接通知到。
转化一下就是
- 从BOSS这个点,最少连几条边可以让整个图连通
就像用一个根节点让一个森林变成一颗树一样,只要把森林里所有树的根结点连接起来即可
所以 如果小弟组成的是一系列的DAG,那么只要连接所有DAG的第一个结点即可
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
/*
* Tarjan算法
* 复杂度O(N+M)
*/
const int MAXN = 510;//点数
const int MAXM = 250000;//边数 至少开n(n-1)/2,
const int N = 510; //缩点后的点数
struct Edge
{
int to,next;
}edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];//Belong数组的值是1~scc
int Index,top;
int scc;//强连通分量的个数
bool Instack[MAXN];
int num[MAXN];//各个强连通分量包含点的个数,数组编号1~scc
//num数组不一定需要,结合实际情况
void addedge(int u,int v)
{
edge[tot].to = v;edge[tot].next = head[u];head[u] = tot++;
}
void Tarjan(int u)
{
int v;
Low[u] = DFN[u] = ++Index;
Stack[top++] = u;
Instack[u] = true;
for(int i = head[u];i != -1;i = edge[i].next)
{
v = edge[i].to;
if( !DFN[v] )
{
Tarjan(v);
if( Low[u] > Low[v] )Low[u] = Low[v];
}
else if(Instack[v] && Low[u] > DFN[v])
Low[u] = DFN[v];
}
if(Low[u] == DFN[u])
{
scc++;
do
{
v = Stack[--top];
Instack[v] = false;
Belong[v] = scc;
num[scc]++;
}
while( v != u);
}
}
void solve(int N)
{
memset(DFN,0,sizeof(DFN));
memset(Instack,false,sizeof(Instack));
memset(num,0,sizeof(num));
Index = scc = top = 0;
for(int i = 1;i <= N;i++)
if(!DFN[i])
Tarjan(i);
}
void init()
{
tot = 0;
memset(head,-1,sizeof(head));
}
int G[N][N];//缩点后的图
int B[N];//存放BOSS可选择的结点
int main()
{
int n,m;
while(cin>>n>>m)
{
init();
for(int i=0;i<m;i++)
{
cin>>B[i];
}
for(int i=1;i<=n;i++)
{
int cnt;cin>>cnt;
while(cnt--)
{
int tp;cin>>tp;
addedge(i,tp);
}
}
solve(n);
memset(G,0,sizeof(G));
/*
*遍历所有Belong[u],建立新的图
*/
for(int u=1;u<=n;u++)
{
int fg=head[u];
while(fg!=-1)//邻接表的访问方法,也可以写成
//for(int i=head[u];i!=-1;i=edge[i].next)
{
Edge &e = edge[fg];
int v = e.to;
//cout<<"Belong["<<u<<"]="<<Belong[u]<<endl;
//cout<<"Belong["<<v<<"]="<<Belong[v]<<endl;
if(Belong[u]!=Belong[v])
G[Belong[u]][Belong[v]]=1;
fg = e.next;
}
}
/*调试用
cout<<"缩点后的"<<endl;
cout<<scc<<endl;
for(int i=1;i<=scc;i++)
for(int j=1;j<=scc;j++)
//printf("%d%s",G[i][j],j==scc?"\n":" ");
*/
/*
*找出所有入度为0的点就是根结点
*只要BOSS不能通知到根节点,就肯定不能通知到所有人
*开始还以为要再写个拓扑排序什么的,
*写了半天还没出来,发现,题目只要求判断能不能通知到
*如果题目还要求,还有几个人没有通知到
*那就要对每个BOSS能通知到的结点遍历
*用总人数 - 通知到的人数,这时会用上上面那个num数组
*/
vector<int> A;//存放每个DAG的根节点
for(int j=1;j<=scc;j++)
{
bool fg=true;
for(int i=1;i<=scc;i++)
{
if(G[i][j])
{
fg=false;
break;
}
}
if(fg)
{
A.push_back(j);
}
}
int AllDag = A.size();
//cout<<"输出所有入度为0的点"<<endl;
//for(int i=0;i<AllDag;i++)cout<<A[i]<<' ';cout<<endl;
bool ans = true;
/*
*点不多,直接写两个循环判断了
*/
for(int i=0;i<AllDag;i++)
{
bool fg=false;
for(int j=0;j<m;j++)
{
if(Belong[B[j]]==A[i])
{
fg=true;
break;
}
}
if(!fg)
{
ans = false;
break;
}
}
//cout<<"输出"<<endl;
if(ans)
cout<<AllDag<<endl;
else
cout<<(-1)<<endl;
}
return 0;
}