强连通分量较难例题【受欢迎的牛 + 最大半联通子图】

这几道题都是 Tarjan 和其他算法的综合,难度较大,含金量较高,特别是第二题。

我的博客:关于 Tarjan 算法的入门博客。

传送门 - 板子题 B3609 题解

开始——


例题一 受欢迎的牛

传送门 - P2314 受欢迎的牛 G

思路

概述: Tarjan + 缩点 DAG + 出入度

1. Tarjan

在一个强连通分量中,很明显,所有奶牛都是互相喜欢的,所以我们分别求出每一个强连通分量,并记录下他们的 size 。

2. 缩点 DAG

我们把强连通分量看成一个超级点,并在相互之间建边。

(图片附在第 3 点中。)

缩点是什么?

如果你没接触过缩点,没有关系,缩点算法博客 + 很好的阅读体验

如何建边?

我们输入时存下每一条边的 u 和 v ,然后现在判断他们是否属于同一强连通分量中,如果不属于,就在他们之间建边。注意,在这之前要先清空结构体 e ,数组 hd 以及变量 cnt 。

这样,缩完点之后它就是一个 DAG ——有向无环图,我们运用的就是它不成环的特质。

但是!!!这道题不用这么麻烦!

我们在思想上把它们重新建边了,但在实现过程中,我们只需要判断两点是否在同一强连通分量里,如果没有,就进行建边之后的操作(具体见第 3 点或代码);如果有,那可以直接忽略。

缩点有什么用?

缩点之后我们可以大大简化这道题了。对于每一个超级点(也就是一个强连通分量),我们有进一步关于出度的操作了。

3. 出度的运用

接下来我们把图看成一个只有 3 个点的 DAG 。

首先放几张做了二十分钟的百万图片 (wtcl)

ps:关于图片,看不清请打开一个新标签页,放大看。由于本人第一次画图,所以丑了点…图 1 中有一条画错的边,已改正,正确边为从 E 到 G 。

原始建边

从上图不难看出,有 3 个强连通分量,我们的第一步操作为反向建边,操作之后图为这样。

图2

出度有什么作用?

它就是我们用来找出符合条件的超级点。

因为每一个超级点要被所有的点(也就是牛)所喜欢,那么它必须满足出度为 0 。

假设我们有一条边为 ( u , v ) (u, v) (u,v) ,表示从 u 到 v ,此时 u 的出度为 1 , v 的出度为 0 。

只有一个超级点出度为 0 ,也就是它没有喜欢的奶牛了,才可能是受欢迎的牛。因为此图没有环,所以它的喜欢不会回来,它也就没有受到所有牛的喜欢。

同时,若该图有两个或两个以上的出度为 0 的点,此题答案依旧为 0 。为什么?还是那句话,因为现在的 DAG 不成环。相信不用我再说,这点已经很好理解了。

为什么要反向建边?

若是正常地建边,我们在计算出度的时候会较为麻烦,所以反向建边,这样一来在统计出度的时候就可以变成“统计入度” 了,相对来说简单很多。

最终,我们的图会变成这样。

图3

这题说到这里,相信已经不用我再说什么了。(好累啊,逃)

数据范围也没有什么特别的,正常设就行。

最后,代码附上。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 50005;
int n, m;
int cnt, hd[maxn];
struct node{
   
	int to, nxt;
}e[maxn * 2];
int dfn[maxn], low[maxn];
int top, st[maxn], de[maxn], si[maxn];
int col, co[maxn];
int tmp;

void add (int u, int v)
{
   
	e[++cnt].to = v;
	e[cnt].nxt = hd[u];
	hd[u] = cnt;
}

void tarjan (int u)
{
   
	dfn[u] = low[u] = ++tmp;
	st[++top] = u;
	for (int i = hd[u]; i; i = e[i].nxt)
	{
   
		int v = e[i].to;
		if (!dfn[v])
		{
   
			tarjan (v);
			low[u] = min (low[u], low[v]);
		}
		else if (!co[v]) low[u] = min (low[u], dfn[v]);
	}
	if (dfn[u] == low[u])
	{
   
		co[u] = ++col;
		++si[col];
		while (st[top] != u)
		{
   
			++si[col];
			co[st[top]] = col;
			--top;
		}
		--top;
	}
}

int main ()
{
   
	scanf ("%d %d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
   
		int u, v;
		scanf ("%d %d", &u, &v);
		add (v, u);
	}
	for (int i = 1; i <= n; i++)
	{
   
		if (!dfn[i]) tarjan (i);
	}
	for (int i = 1; i <= n; i++)
	{
   
		for (int j = hd[i]; j; j = e[j].nxt)
		{
   
			int v = e[j].to;
			if (co[i] != co[v]) de[co[v]]++;
		}
	}
	int ans, u;
	ans = u = 0;
	for (int i = 1; i <= col; i++)
	{
   
		if 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值