题意: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;
}