洛谷 P2746/AcWing 367 [USACO5.3]校园网Network of Schools
好久没更了,更一下~
这题真的胡乱瞎写,WA * 3
零、原题链接
luogu
AcWing
一、简要题意
给定一个具有\(n\) 个点的有向图,需要完成两个任务:
Task 1 求出这个图至少要给几个点信息,利用有向边的传递关系,可传递至每一个节点。
Task 2 求出这个图至少要添加几条有向边,使得这个图变成只有一个包含所有点的连通分量。
\(2 ≤ n ≤ 100\)
二、解法
Task 1
这个任务很简单,将该图缩完点后求出有多少个入度为 \(0\) 的点即可。
证明:
因为所有入度不为\(0\) 的点一定会有从其他点向它传输的边,所以全部汇集上去,只有入度为 \(0\) 的点没有其他点传递信息给它。
Task 2
这个是上一个任务的升级版。
如果想要变成一个大的连通分量,肯定需要将所有入度/出度为 \(0\) 的点全部改掉。
所以添加最少的方法就是把一个出度为 \(0\) 的点连向一个入度为 \(0\) 的点,这样一下可以改变 \(2\) 个状态。
三、code
缩点使用Tarjan算法。
注意本题的输入格式较为奇怪。
// by pjx Feb.
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <stack>
#define REP(i, x, y) for(register int i = x; i < y; i++)
#define rep(i, x, y) for(register int i = x; i <= y; i++)
#define PER(i, x, y) for(register int i = x; i > y; i--)
#define per(i, x, y) for(register int i = x; i >= y; i--)
using namespace std;
const int N = 105;
int n, totin, totout;
int dfn[N], low[N], timetag;
int b[N];
int in[N], out[N];
int sta[N], top;
int cnt, id[N], len[N], ans1, ans2;
vector <int> g[N];
void tarjan(int x)//tarjan模板,下面的//行都是我第一遍写错的
{
timetag++;//
dfn[x] = timetag;
low[x] = timetag;
sta[++top] = x;
b[x] = 1;
for(int i = 0; i < g[x].size(); i++)
{
int y = g[x][i];
if(!dfn[y])
{
tarjan(y);
low[x] = min(low[x], low[y]);//
}
else if(b[y] == 1)
{
low[x] = min(low[x], dfn[y]);
}
}
if(dfn[x] == low[x])
{
cnt++;
int y = sta[top];
top--;
b[y] = 0;
id[y] = cnt;
len[cnt]++;//
while(x != y)
{
y = sta[top];
top--;
b[y] = 0;
id[y] = cnt;
len[cnt]++;
}
}
}
int main()
{
cin >> n;
rep(i, 1, n)
{
int x;
cin >> x;
while(x != 0)
{
g[i].push_back(x);
cin >> x;
}
}
rep(i, 1, n)
{
if(!dfn[i])
{
tarjan(i);
}
}
rep(i, 1, n)//注意这里是n(WA的教训)
{
for(int j = 0; j < g[i].size(); j++)
{
int y = g[i][j];
int ida = id[i];
int idb = id[y];
if(ida != idb)
{
in[idb]++;
out[ida]++;
}
}
}
if(cnt == 1)
{
cout << "1" << endl;
cout << "0";
return 0;
}
rep(i, 1, cnt)
{
if(!in[i])//统计入度为0的点个数
{
totin++;
}
if(!out[i])//统计出度为0的点个数
{
totout++;
}
}
cout << totin << endl; //Task 1,求入度节点个数
cout << max(totin, totout);//Task 2,求最少添加多少次有向边可以得到只有一个连通分量
return 0;
}