(强连通分量)洛谷P2341【模板】强连通分量 / [HAOI2006]受欢迎的牛

洛谷P2341【模板】强连通分量 / [HAOI2006]受欢迎的牛

思路:

如题目描述,模板题。
花了两个小时看了tarjan,然后看了题目。what?excuse me?为啥跟我看到的东西感觉不太一样?然后看了大佬的题解,what?为啥叫容易看出,为啥我看不出?
啥叫强连通分量呢,简而言之,一个图的子图中任意两点可以相互到达。(就是构成了一个环)。
下面说tarjan算法。
其中两个重要的数组:
dfn:搜索的次序;low:这个点及其子孙节点中dfn的最小值。就是确定这个点所属的强连通分量。low相等就是这几个点所处于同一个强连通分量。
我们用栈stack记录搜素的路径,vis记录是否已经访问过。
在一个结点出边遍历完了之后,我们回溯判断low。如果dfn[x]==low[x]则可以看作这是某一强连通分量的根节点。然后不断出栈,直到x被弹出。

tarjan核心代码:

void tarjan(int u)
{
	int i;
	cnt++;
	dfn[u]=low[u]=cnt;
	s.push(u);
	vis[u]=1;
	for(i=0;i<edge[u].size();i++)
	{
		int v=edge[u][i];
		if(!dfn[v])
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v])
			low[u]=min(low[u],dfn[v]);
		
	}
	if(dfn[u]==low[u])
	{
		tot++;
		int pre;
		do
		{
			pre=s.top();
			s.pop();
			vis[pre]=0;
			ans[tot].push_back(pre);
		}while(pre!=u);
	}
}

对于这题,我们容易看出……
我们可以看出如果牛互相爱慕能够构成一个强连通分量(环),那么之外的任意一头牛爱慕其中的一个牛,就能爱慕这个分量中其他的牛;同理,这个分量中某头牛爱慕其他的牛,那么这个分量中的其他的牛也会爱慕那个牛。
然后我们可以缩点,把图变成有向无环图。
接着可以发现,如果一个强连通分量的出度为0,那么这个强连通分量中牛的个数就是答案。因为这是有向无环图,所以如果一个强连通分量能到另外一个分量,那么他一定不会被另外的分量到达(听起来好绕)。就比如两个分量a,b,如果存在a->b,那么不可能存在b->a,因为我们已经通过缩点把环都给缩成了一个点。
所以我们只要找出度为0的强连通分量就行了,但是如果有多个强连通分量的出度都为0,那么就没有牛成为明星,因为不是所有的牛都爱慕他(们)。这点很好理解。
我们把在同一个强连通分量的点用color标好。用out记录出度。

代码:

#include<bits/stdc++.h>
#define pii pair<int,int>
#define ll long long
#define cl(x) memset(x,0,sizeof(x))
const int N=1e6+10;
const int mod=1e7+9;
const int maxn=0x3f3f3f3f;
const int minn=0xc0c0c0c0;
const int inf=99999999;
using namespace std;
struct edge
{
	int next,to;
}a[N];
int head[11000]={0}, dfn[11000]={0},low[11000],vis[11000]={0},out[11000],color[11000],sum[11000]={0};
int len=0,tot=0,cnt=0;
stack<int> s;
void add(int u,int v)
{
	a[++len]={head[u],v};
	head[u]=len;
}
void tarjan(int u)
{
	int i;
	cnt++;
	dfn[u]=low[u]=cnt;
	s.push(u);
	vis[u]=1;
	for(i=head[u];i;i=a[i].next)
	{
		int v=a[i].to;
		if(!dfn[v])
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v])
			low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u])
	{
		tot++;
		int pre;
		do
		{
			pre=s.top();
			s.pop();
			vis[pre]=0;
			color[pre]=tot;
			sum[tot]++;
		}while(pre!=u);
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int n,m,i,j;
	cin>>n>>m;
	for(i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		add(u,v);
	}
	for(i=1;i<=n;i++)
		if(!dfn[i])
			tarjan(i);	
	for(i=1;i<=n;i++)
		for(j=head[i];j;j=a[j].next)
			if(color[i]!=color[a[j].to])
				out[color[i]]++;
	int ans=0;
	for(i=1;i<=tot;i++)
	{
		if(out[i]==0)
		{
			if(ans)
			{
				cout<<"0"<<endl;
				return 0;
			}
			ans=i;
		}
	}
	cout<<sum[ans]<<endl;
	return 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$ 位置上的值; - 在插入元素时,需要将离散化后的数列的元素从右往左依次插入树状数组中,而不是从左往右。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值