洛谷 P2341 [HAOI2006]受欢迎的牛(强连通分量)

23 篇文章 1 订阅

题目背景

本题测试数据已修复。

题目描述

每头奶牛都梦想成为牛棚里的明星。被所有奶牛喜欢的奶牛就是一头明星奶牛。所有奶

牛都是自恋狂,每头奶牛总是喜欢自己的。奶牛之间的“喜欢”是可以传递的——如果A喜

欢B,B喜欢C,那么A也喜欢C。牛栏里共有N 头奶牛,给定一些奶牛之间的爱慕关系,请你

算出有多少头奶牛可以当明星。

输入输出格式

输入格式:

 

 第一行:两个用空格分开的整数:N和M

 第二行到第M + 1行:每行两个用空格分开的整数:A和B,表示A喜欢B

 

输出格式:

 

 第一行:单独一个整数,表示明星奶牛的数量

 

输入输出样例

输入样例#1: 

3 3
1 2
2 1
2 3

输出样例#1: 

1

说明

只有 3 号奶牛可以做明星

【数据范围】

10%的数据N<=20, M<=50

30%的数据N<=1000,M<=20000

70%的数据N<=5000,M<=50000

100%的数据N<=10000,M<=50000

解题思路

如果喜欢一个强连通分量里的任意一只牛,那就是喜欢里面的所有牛,如果强连通分量里的任意一只牛喜欢令一只牛,那么强连通分量里的所有牛都喜欢那只牛,所以可以把一个强连通分量视为一个点,把所有强连通分量都视为一个点之后,图中就不再存在环。当存在且只存在一个出度为0的强连通分量时,那个强连通分量中的所有牛就是明星牛。假设一个强连通分量里的牛是明星牛,就说明他们被所有牛喜欢,如果他们也喜欢其他的牛,那么就有环,而此时图中是不存在环的,所以不可能。如果存在多个出度为0的强连通分量,分量中的牛不喜欢其他的牛,那就不会有牛被所有牛喜欢了。

代码如下

#include <iostream>
#include <vector>
#include <cstring>
#include <queue>
#include <stack>
#include <cmath>
#define maxn 10005
using namespace std;
vector<int> g[maxn];
bool vis[maxn];
int dfn[maxn], low[maxn];
int unit[maxn];
stack<int> sta;
int id;
int cnt;
int ans;
bool tarjan(int x)
{
	id ++;
	dfn[x] = low[x] = id;
	sta.push(x);
	vis[x] = true;
	for(int i = 0; i < g[x].size(); i ++){
		int y = g[x][i];
		if(!dfn[y]){
			if(!tarjan(y))
				return false;
			low[x] = min(low[x], low[y]);
		}
		else if(vis[y])
			low[x] = min(low[x], dfn[y]);
	}
	if(dfn[x] == low[x]){
		queue<int> que;
		cnt ++;
		while(!sta.empty()){
			int top = sta.top();
			sta.pop();
			vis[top] = false;
			unit[top] = cnt; //记录这些牛属于哪个强连通分量 
			que.push(top);   //存此分量中的元素 
			if(top == x)
				break;
		}
		bool flg = true;
		int temp = que.size();
		while(!que.empty()){   //判断此强连通分量的出度是否为0 
			int top = que.front();
			que.pop();
			for(int i = 0; i < g[top].size(); i ++){
				int r = g[top][i];
				if(unit[r] != unit[top]){  //判断是不是属于同一个强连通分量 
					flg = false;
					break;
				}
			} 
		}
		if(flg){   //如果这个强连通分量出度为0 
			if(!ans)  //第一个出度为0的 
				ans = temp;  //存个数 
			else {     //如果不止一个 
				ans = 0; //那么没有明星奶牛 
				return false;  //结束递归 
			}
		}
	}
	return true;	
}
int main()
{
	int n, m;
	cin >> n >> m;
	for(int i = 1; i <= n; i ++)
		g[0].push_back(i);
	for(int i = 0; i < m; i ++){
		int a, b;
		cin >> a >> b;
		g[a].push_back(b);
	}
	id = 0;
	cnt = 0;
	ans = 0;
	memset(vis, 0, sizeof(vis));
	memset(dfn, 0, sizeof(dfn));
	memset(unit, 0, sizeof(unit));
	tarjan(0);	
	cout << ans << endl;
	return 0;
} 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这道题目还可以使用树状数组或线段树来实现,时间复杂度也为 $\mathcal{O}(n\log n)$。这里给出使用树状数组的实现代码。 解题思路: 1. 读入数据; 2. 将原数列离散化,得到一个新的数列 b; 3. 从右往左依次将 b 数列中的元素插入到树状数组中,并计算逆序对数; 4. 输出逆序对数。 代码实现: ```c++ #include <cstdio> #include <cstdlib> #include <algorithm> const int MAXN = 500005; struct Node { int val, id; bool operator<(const Node& other) const { return val < other.val; } } nodes[MAXN]; int n, a[MAXN], b[MAXN], c[MAXN]; long long ans; inline int lowbit(int x) { return x & (-x); } void update(int x, int val) { for (int i = x; i <= n; i += lowbit(i)) { c[i] += val; } } int query(int x) { int res = 0; for (int i = x; i > 0; i -= lowbit(i)) { res += c[i]; } return res; } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) { scanf("%d", &a[i]); nodes[i] = {a[i], i}; } std::sort(nodes + 1, nodes + n + 1); int cnt = 0; for (int i = 1; i <= n; ++i) { if (i == 1 || nodes[i].val != nodes[i - 1].val) { ++cnt; } b[nodes[i].id] = cnt; } for (int i = n; i >= 1; --i) { ans += query(b[i] - 1); update(b[i], 1); } printf("%lld\n", ans); return 0; } ``` 注意事项: - 在对原数列进行离散化时,需要记录每个元素在原数列中的位置,便于后面计算逆序对数; - 设树状数组的大小为 $n$,则树状数组中的下标从 $1$ 到 $n$,而不是从 $0$ 到 $n-1$; - 在计算逆序对数时,需要查询离散化后的数列中比当前元素小的元素个数,即查询 $b_i-1$ 位置上的值; - 在插入元素时,需要将离散化后的数列的元素从右往左依次插入树状数组中,而不是从左往右。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值