洛谷 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;
}


posted @ 2021-02-14 23:04  panjx  阅读(53)  评论(0编辑  收藏  举报