(tarjan)洛谷 P2194 HXY烧情侣

12 篇文章 0 订阅
9 篇文章 0 订阅

思路:费用就是每个强连通分量里最小值的和,方案数就是每个强连通分量里最小值数量的乘积

 

众所周知,HXY 已经加入了 FFF 团。现在她要开始喜(sang)闻(xin)乐(bing)见(kuang)地烧情侣了。

这里有 nnn 座电影院,nnn 对情侣分别在每座电影院里,然后电影院里都有汽油,但是要使用它需要一定的费用。mmm 条单向通道连接相邻的两对情侣所在电影院。

HXY 有个绝技,如果她能从一个点开始烧,最后回到这个点,那么烧这条回路上的情侣的费用只需要该点的汽油费即可。并且每对情侣只需烧一遍,电影院可以重复去。然后她想花尽可能少的费用烧掉所有的情侣。

问:最少需要多少费用,并且当费用最少时的方案数是多少?由于方案数可能过大,所以请输出方案数对 109+710^9+7109+7 取模的结果。

(注:这里 HXY 每次可以从任何一个点开始走回路。就是说一个回路走完了,下一个开始位置可以任选。所以说不存在烧不了所有情侣的情况,即使图不连通,HXY 自行选择顶点进行烧情侣行动。且走过的道路可以重复走。)

输入格式

第一行一个正整数 nnn。
第二行 nnn 个正整数,表示每个点的汽油费 wiw_iwi​。
第三行一个正整数 mmm。
接下来 mmm 行,每行两个正整数 xi,yix_i,y_ixi​,yi​,表示一条 xi→yix_i \to y_ixi​→yi​ 的单向道路。

输出格式

输出一行两个整数,分别表示最小花费,和花费最小时的方案数。

输入输出样例

输入 #1

3
1 2 3
3
1 2
2 3
3 2

输出 #1

3 1

输入 #2

3
10 20 10
4
1 2
1 3
3 1
2 1

输出 #2

10 2

说明/提示

对于 30%30\%30% 的数据,1≤n,m≤201\le n,m \le 201≤n,m≤20;
对于另外 10%10\%10% 的数据,保证不存在回路;
对于 100%100\%100% 的数据,1≤n≤1051\le n \le 10^51≤n≤105,1≤m≤3×1051\le m \le 3\times 10^51≤m≤3×105,1≤wi≤1091\le w_i \le 10^91≤wi​≤109。

#include <cstdio>
#include<iostream>
#include <cstring>
#include <algorithm>
#include <stack>
#include<set>
#include<vector>
using namespace std;
#define ll long long
const int maxn=505000;
#define mod 1000000007
stack<int>q;
set<int>ans;
vector<int>a[maxn];
int cnt,n,m,head[maxn],dfn[maxn],low[maxn],co[maxn],tot,id,out[maxn],root;
struct Edge
{
    int to,next;
}e[maxn];
void add(int u,int v)
{
    e[cnt].to=v;
    e[cnt].next=head[u];
    head[u]=cnt++;
}
void init()
{
    ans.clear();
    for(int i=0;i<maxn;i++) a[i].clear();
    memset(out,0,sizeof(out));
    memset(co,0,sizeof(co));
    memset(head,-1,sizeof(head));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    while(!q.empty()) q.pop();
    cnt=0;id=0,tot=0;
}
void tarjan(int u)
{
    q.push(u);
    dfn[u]=low[u]=++tot;int ch=0;
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int v=e[i].to;
        if(!dfn[v])
        {
            ch++;
            tarjan(v);
            low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[u]&&u!=root) ans.insert(u);
        }
        else if(!co[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(root==u&&ch>=2) ans.insert(u);
    if(low[u]==dfn[u])
    {
        id++;
        while(q.top()!=u)
        {
            int x=q.top();
            q.pop();
            co[x]=id;
        }
        co[u]=id;
        q.pop();
    }
}
int w[maxn];
int main()
{
    scanf("%d",&n);
    init();
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d %d",&u,&v);
        add(u,v);//add(v,u);
    }
    for(int i=1;i<=n;i++)
    {
        if(dfn[i]==0) {
                root=i;
                tarjan(i);
        }
    }
    /*for(int i=1;i<=n;i++)
    {
        printf("co[%d]=%d\n",i,co[i]);
    }*/
    for(int i=1;i<=n;i++) a[co[i]].push_back(w[i]);
    ll p1=0,p2=1;
    for(int i=1;i<=id;i++)
    {
        ll p=0;
        sort(a[i].begin(),a[i].end());
        p1+=a[i][0];
        for(int u:a[i])
        {
            if(u==a[i][0]) p++;
        }
        p2*=p;p2%=mod;
    }
    printf("%lld %lld\n",p1,p2);
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值