CSDN同步

原题链接

POJ的链接

简要题意:

给定一张图,求多少个点,每个点都能到达它。

本题作为强连通分量的入门题。

何为强连通分量?有什么用?

下面一一解释。

首先,我们要确认,这道题目如果不用强连通分量而用其它方法(比如说暴力)的话:

时间复杂度将达到 \(O(n^2)\),此时不易通过,也非正解。

强连通分量是什么?我们来看一张图吧。

我们希望,如果能把环通过某种方式去掉,然后变成有向无环图,就很容易了。

一个强连通分量中的点两两可达。(有向图中)一个点也可以被认为是一个强连通分量。

也就是说,你会发现,一个环 或者 若干个相交的环 都会变成一个强连通分量。

显然它们两两可达,最后图会变成若干个强连通分量。

你会发现,\(1\) 是一个强连通分量,\(2,3,4,5\) 是一个强连通分量,\(6\),\(7\),\(8\) 均为一个单独的强连通分量。

此时我们可以简化这个图变成:

\(qx\) 表示 \(x\) 号强连通分量。

此时你发现 \(q4\) 就是答案,里面装的是 \(7\),所以答案为 \(1\).

那么问题在于,如何求强连通分量?

回到这个图。

用 \(dfn_i\) 表示 \(i\) 的遍历编号(就是我们常说的 “时间戳”),\(f_i\) 为 \(i\) 号节点属于的强连通分量编号,\(low_i\) 为 \(i\) 号节点能走到的 \(dfn\) 值最小的节点。

一开始 \(low_i = i (1 \leq i \leq n)\).

首先,我们从 \(1\) 节点开始遍历,走到 \(2\).

然后走到了 \(3,4,5\) ,没有问题。此时 \(dfn_i = i (1 \leq i \leq 5)\)

然后,\(5\) 走到 \(2\) ,说明什么?

说明出现了环,说明出现了一个强连通分量(不一定完整,也就是不一定是一个完整的强连通分量)!

此时,\(dfn_i = 2 (3 \leq i \leq 5)\),这是需要更新的。那么此时第一个强连通分量产生了:

\(low_i = 1 (2 \leq i \leq 5)\).

然后你回溯,发现 \(5\) 没有其它节点可走。因为它属于一个未确定的强连通分量,因此保留。

同样,回溯到 \(4\),继续到 \(3\),走到了 \(6\).

继续走到 \(7\). 此时 \(7\) 没有其它节点可走,并且 \(low_7 = 7\),说明 它只能走到自己,也不存在别人能走到它——因为它没有节点可走。所以它是一个单独的强连通分量,我们把它标记,即丢弃。

然后,回溯到 \(6\).发现 \(6\) 也没有其它节点可走(\(7\) 已经被标记丢弃了),所以同理,\(6\) 也是一个强连通分量,标记丢弃掉。

接着回溯到 \(3\),回溯到 \(2\).

然后 \(2\) 走到了 \(8\),发现 \(8\) 也没有其它节点可走(\(7\) 已经被标记丢弃了),所以同理,\(8\) 也是一个强连通分量,标记丢弃掉。

然后回溯到 \(2\),发现 \(2\) 没有可以走的节点了。所以,和 \(2\) 出于同一个强连通分量的节点全部被标记丢弃掉。

此时回溯到 \(1\),\(1\) 也作为了一个单独的强连通分量。

此时强连通分量就求出来了。

那么你会说,这些过程有一个难维护的细节:就是 “同一个强连通分量的节点全部被标记丢弃掉”,难道还要扫一遍吗?

不用。我们可以用栈维护。每走过一个节点进栈,标记丢弃则出栈。

inline void dfs(int u) {
dfn[u]=low[u]=++times;
s.push(u); h[u]=1;
for(int i=0,t;i<G[u].size();i++) {
t=G[u][i];
if(!dfn[t]) {
dfs(t); low[u]=min(low[u],low[t]); // 如果 t 能往上,那么 u 也能
} else if(h[t]) low[u]=min(low[u],dfn[t]); // 否则直接统计
}
if(low[u]==dfn[u]) { //表示当前节点无法向上,则统计答案
cnt++; int k;
do {
// a[cnt].push_back(s.top());
k=s.top();
h[k]=0; f[k]=cnt;
gs[cnt]++; s.pop();
} while(k!=u) ;
}
}

此时,重构的图(代码中未重构)

那么,如何维护最终的答案?

你会发现,如果按照强连通分量重新构图,则一定是 有向无环图 。(不一定是树,因为有可能是菱形状)

下面证明两个结论:

  • 如果有 \(\geq 2\) 个点出度为 \(0\),则整张图不存在答案。

证明:

如果有 \(\geq 2\) 个点出度为 \(0\),则首先除了这 \(2\) 个点外的其它所有点都不会是答案。因为这 \(2\) 个点就无法到达它们。

然后,这两个点也不是答案。因为,它们互相无法到达,也就促使了互相都不是答案。得证。

  • 如果只有 \(1\) 个点出度为 \(0\),则这个答案就是它。

证明:

首先 \(y\) 不连向其它任何点,所以,整张图的答案要么是它,要么不是它。

如果这个结论不成立的话,结合上面的结论,你会发现答案始终为 \(0\).

但是样例告诉我们,不是这样的。所以得证。

这个证明可能有点不太严谨,但是考场上这样证就足够了

但是我们不需要重构这个图。

因为,我们这需要在原图上查边 \(u \rightarrow v\),如果 \(f_u \not = f_v\),说明不属于同一个强连通分量,就 \(du_{f_u} \gets du_{f_u} + 1\),\(du_i\) 表示第 \(i\) 个强连通分量 缩点后 的出度。

另:把每个强连通分量看做一个点,这样的方法叫做缩点。

最后统计出度即可。

时间复杂度:\(O(n + m)\).

实际得分:\(100pts\).

#pragma GCC optimize(2)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack> //由于 POJ 不支持万能头,因此要手写
using namespace std; const int N=1e5+1; inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;} bool h[N]; int cnt=0;
int times,dfn[N],du[N];
int low[N],n,m,f[N],gs[N];
vector<int>G[N];
stack<int>s; inline void dfs(int u) {
dfn[u]=low[u]=++times;
s.push(u); h[u]=1;
for(int i=0,t;i<G[u].size();i++) {
t=G[u][i];
if(!dfn[t]) {
dfs(t); low[u]=min(low[u],low[t]);
} else if(h[t]) low[u]=min(low[u],dfn[t]);
}
if(low[u]==dfn[u]) {
cnt++; int k;
do {
// a[cnt].push_back(s.top());
k=s.top();
h[k]=0; f[k]=cnt;
gs[cnt]++; s.pop();
} while(k!=u) ;
}
} //Tarjan 模板 int main(){
n=read(),m=read();
while(m--) {
int x=read(),y=read();
G[x].push_back(y);
} for(int i=1;i<=n;i++)
if(!dfn[i]) dfs(i);
for(int u=1;u<=n;u++)
for(int i=0;i<G[u].size();i++) {
int x=G[u][i];
if(f[u]-f[x]) du[f[u]]++;
}
// for(int i=1;i<=n;i++) printf("%d ",f[i]); putchar('\n');
// for(int i=1;i<=cnt;i++) printf("%d ",gs[i]); putchar('\n');
// for(int i=1;i<=n;i++) printf("%d ",du[i]); putchar('\n');
int ans=0;
for(int i=1;i<=cnt;i++)
if(!du[i]) {
if(ans) {puts("0");return 0;} //已经有入度为 0 的,再来一个,答案为 0,结束
ans=i; //记录入度为 0 强连通分量的编号
} printf("%d\n",gs[ans]); //强连通分量的大小即为答案
return 0;
}

P2341 [USACO03FALL][HAOI2006]受欢迎的牛 G 题解的更多相关文章

  1. 洛谷P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G (tarjan缩点)

    在本题中很明显,给你一个有向图,要用tarjan缩点. 缩点后,一头牛要受到所有牛的欢迎,那么该点的出度要为0,这是容易证明的:如果该点还有出度,比如a连向b,那么a不受到b的欢迎.所以我们要找出度为 ...

  2. 【luogu P2341 [HAOI2006]受欢迎的牛】 题解

    题解报告:https://www.luogu.org/problemnew/show/P2341 我们把图中的强连通分量缩点,然后只有出度为0的牛是受欢迎的,这样如果出度为0的牛只有一个,说明受所有牛 ...

  3. 题解【洛谷P2341】 [HAOI2006]受欢迎的牛

    题面 题解 \(Tarjan\)缩点后统计每个点的出度. 如果有多个点出度为\(0\),就直接输出\(0\),否则输出出度为\(0\)的强连通分量里的点数. 代码 #include <iostr ...

  4. 洛谷P2341 [HAOI2006]受欢迎的牛 (Tarjan,SCC缩点)

    P2341 [HAOI2006]受欢迎的牛|[模板]强连通分量 https://www.luogu.org/problem/P2341 题目描述 每头奶牛都梦想成为牛棚里的明星.被所有奶牛喜欢的奶牛就 ...

  5. 洛谷 P2341 [HAOI2006]受欢迎的牛 解题报告

    P2341 [HAOI2006]受欢迎的牛 题目描述 每头奶牛都梦想成为牛棚里的明星.被所有奶牛喜欢的奶牛就是一头明星奶牛.所有奶 牛都是自恋狂,每头奶牛总是喜欢自己的.奶牛之间的"喜欢&q ...

  6. P2341 [HAOI2006]受欢迎的牛

    P2341 [HAOI2006]受欢迎的牛 塔尔羊标准模板(我才不会告诉你我嘴里含着一个九省联考的出题人) 不会劈配.林克卡特树.制胡窜 我还会叉粪宿主,梳妆素组,西安段素 #include<c ...

  7. P2341 [HAOI2006]受欢迎的牛(tarjan+缩点)

    P2341 [HAOI2006]受欢迎的牛 题目描述 每头奶牛都梦想成为牛棚里的明星.被所有奶牛喜欢的奶牛就是一头明星奶牛.所有奶 牛都是自恋狂,每头奶牛总是喜欢自己的.奶牛之间的“喜欢”是可以传递的 ...

  8. 洛谷——P2341 [HAOI2006]受欢迎的牛//POJ2186:Popular Cows

    P2341 [HAOI2006]受欢迎的牛/POJ2186:Popular Cows 题目背景 本题测试数据已修复. 题目描述 每头奶牛都梦想成为牛棚里的明星.被所有奶牛喜欢的奶牛就是一头明星奶牛.所 ...

  9. P2341 [HAOI2006]受欢迎的牛(更完)

    P2341 [HAOI2006]受欢迎的牛 题解 tarjan 缩点板子题 如果 A 稀饭 B,那就 A 向 B 连边,构造出一个有向图 如果这个有向图里有强连通分量,也就说明这个强连通分量里的所有奶 ...

随机推荐

  1. Ubunt 16.04 安装 Beyond compare 4

    1. 下载安装包: 2. 安装步骤 3. 运行并注册 之前Beyond compare 3 只有32位,在Ubunt 16.04上运行效率非常低,所以只有安装最新的Beyond compare 4,安 ...

  2. 能源科技,苹果和Google的新圣战?

    细心的果粉可能会注意到,最新版本的IOS软体中,增加了一个不起眼的按钮,它是一款署名为"家庭"的App,之所以说它不起眼,是因为它好像真得没什么用,活跃率恐怕不及Wechat的万分 ...

  3. Java入门教程九(封装继承多态)

    封装 封装就是将对象的属性和方法相结合,通过方法将对象的属性和实现细节保护起来,实现对象的属性隐藏.做法就是:修改属性的可见性来限制对属性的访问,并为每个属性创建一对取值(getter)方法和赋值(s ...

  4. [Abp vNext 源码分析] - 19. 多租户

    一.简介 ABP vNext 原生支持多租户体系,可以让开发人员快速地基于框架开发 SaaS 系统.ABP vNext 实现多租户的思路也非常简单,通过一个 TenantId 来分割各个租户的数据,并 ...

  5. Asp.NET MvC EF实现分页

    打开Visual Studio 2017 选择 项目----->管理nuget包  其他版本也有 输入paged 下载安装 pagedList和pagedList.mvc 在model文件新建一 ...

  6. docker 编译开发代码做镜像

    文件目录 Dockerfile 是docker制作镜像的文件,docker_run.sh是sh文件,gin_test是go编译之后的linux可执行程序,gintest.env是配置文件夹 首先写一个 ...

  7. javaee作业

    一.单选题(共5题,50.0分) 1 在SqlSession对象的openSession()方法中,不能作为参数executorType的可选值 的是(     ).   A. ExecutorTyp ...

  8. vue的$message(提示框换行)

    之前一直在搜怎么让提示框的文字换行,网上搜到的基本都是使用 ‘ /n ’,使用无效,也试了css换行,本来想用弹窗自己编辑html内容,还好回去官网看了一下: let arr = ['测试一', '测 ...

  9. (转)C++对象的内存布局

    原文地址:http://blog.csdn.net/haoel/article/details/3081328 C++ 对象的内存布局 陈皓 http://blog.csdn.net/haoel 前言 ...

  10. 何为内存模型(JMM)?

    前言 任何一门语言都有其语言规范,从逻辑上我们可划分为语法规范和语义规范,语法规范则是描述了如何通过相关语法编写可执行的程序,而语义规范则是指通过语法编写的程序所构造出的具体含义.语言只要具备存储(比 ...