Codeforces 427C Checkposts 强连通分量(tarjan)

点击打开链接

题意:n个点,m条(n<=1e5,m<=3e5) 在点i设置checkpost 需要价钱c[i] &&能保护所有点j (i能到j,j能到i),问所以点都能被保护时所需要的最小价钱,以及在设置点最小,钱最小下的方法数?

思路:设置点最少:对于同一个强连通分量只要设置一个点即可,钱最少选出每个强连通分量中最小的c[i],方法数为每个SCC中最小c[i]个数的乘积.

资料

Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一颗子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以盘对栈顶到栈中的节点是否为一个强连通分量

考虑强联通分量C 其中第一个被发现的点为x,则C中其他的点v都为x的后代 .

结论1:若low[u]==dfn[u] 则结点u为某个强联通分量的根。

反证:若w为强联通分量的根,w!=u 则w为u的祖先,low[u]<dfn[u] 出现矛盾.结论得证

结论2:若回溯到根u 则栈顶到u 为一个强连通分量.

若v与u不属于同一个强连通分量,v所在的强连通分量的根为fv.因为fv为u的后代,回溯到u之前,以fv为根的强联通分量已经退栈.


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int N=3e5+20;
const int inf=1e5;
vector<int> e[N];
int n,m,c[N];
int dfn[N];//dfn[u] 结点u在dfs树中的编号 
int low[N];//low[u] 从结点u或者u的子孙出发能到底的祖先的最小编号 
int InStack[N];
vector<int> Scc[N];//Scc[i]第i个强连通分量中的结点 
int index,Scc_num;
stack<int> sta;
void init()
{
	index=Scc_num=0;
	for(int i=1;i<=n;i++)
	{
		dfn[i]=low[i]=0;
		InStack[i]=0;
		e[i].clear();
		Scc[i].clear();
		scanf("%d",&c[i]);//
	}
	cin>>m;
	for(int i=0;i<m;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		e[u].push_back(v);
	}
	while(!sta.empty())
		sta.pop();
}
void tarjan(int u)
{
	dfn[u]=low[u]=++index;
	sta.push(u);
	InStack[u]=1;
	for(int i=0;i<e[u].size();i++)
	{
		int v=e[u][i];
		if(dfn[v]==0)
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		//v在栈中,则v为u的祖先,(u,v)为回边 
		else if(InStack[v]==1)
		{
			low[u]=min(low[u],dfn[v]);
		}
	}
	//u为Scc的根
	if(low[u]==dfn[u])
	{
		++Scc_num;
		//栈顶~u为同一个联通分量 
		while(!sta.empty())
		{
			int v=sta.top();
			sta.pop();
			InStack[v]=0;//
			Scc[Scc_num].push_back(c[v]);
			if(v==u)
			break;
		}	
	} 
	
}
int main()
{
	while(cin>>n)
	{
		init();
		for(int i=1;i<=n;i++)
		{
			if(!dfn[i])
				tarjan(i);
		}
		ll ans=1,Min=0;
		for(int i=1;i<=Scc_num;i++)
		{
			ll x,cnt=0;
			sort(Scc[i].begin(),Scc[i].end());
			for(int j=0;j<Scc[i].size();j++)
			{
				if(j==0)
				{
					x=Scc[i][j];
					Min+=x;
					cnt++;
				}
				else if(x==Scc[i][j])
				cnt++;
				else
				break;
			}
			ans=(ans*cnt)%mod;
		}
		cout<<Min<<' '<<ans<<endl;
	}
	return 0;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值