P2194 HXY烧情侣【Tarjan】

前言

当时和\(GYZ\)大佬一起做这个题,他表示这个题对他很不友好(手动滑稽)

题目描述

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

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

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

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

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

输入格式

第一行一个正整数 \(n\)
第二行 \(n\) 个正整数,表示每个点的汽油费 \(w_i\)
第三行一个正整数 \(m\)
接下来 \(m\) 行,每行两个正整数 \(x_i,y_i\) ,表示一条 \(x_i \to y_i\) 的单向道路。

输出格式

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

输入输出样例

输入 #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\%\) 的数据,\(1\le n,m \le 20\)
对于另外 \(10\%\) 的数据,保证不存在回路;
对于 \(100\%\) 的数据,\(1\le n \le 10^5\)\(1\le m \le 3\times 10^5\)\(1\le w_i \le 10^9\)

分析

看到题目中从一个点烧,可以连带烧掉这一条回路上的所有情侣,所以就是一个\(Tarjan\)缩点的问题。在缩点的时候把每个强连通分量里最小的点权记录为这一强连通分量的最小权。然后取完\(min\)之后(这里的取\(min\)是当他小于他的时候更新),下边判断一下是否相等,因为小于的时候才会更新,等于的时候不会更新,所以直接让相等的数量++。最后直接统计答案就可以了。(其实根本不用取模,到不了那么大,但是还是写上比较好)

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5+10;
int head[maxn],low[maxn],dfn[maxn];
struct Node{
    int v,next;
}e[maxn<<1];
int val[maxn];
int sum[maxn];
int rd[maxn];
int  ans;
int vis[maxn],c[maxn];
int cnt1[maxn];
int tot,cnt,num,top,sta[maxn];
void Add(int x,int y){//建边
    e[++tot].v = y;
    e[tot].next = head[x];
    head[x] = tot;
}
void Tarjan(int x){//Tarjan缩点
    sta[++top] = x;
    dfn[x] = low[x] = ++num;
    vis[x] = 1;
    for(int i=head[x];i;i = e[i].next){
        int v=e[i].v;
        if(!dfn[v]){
            Tarjan(v);
            low[x] = min(low[x],low[v]);

        }
        else if(vis[v]){
            low[x] = min(low[x],dfn[v]);
        }
    }//以上都是基操
    if(dfn[x] == low[x]){
        cnt++;
        int y;
        while(1){
            y=sta[top--];
            c[y] = cnt;
            vis[y] = 0;
            if(sum[cnt] > val[y]){//取最小值,等于的话不取,到下边判断
                sum[cnt] = val[y];
                cnt1[cnt] = 0;
            }
            if(sum[cnt] == val[y])cnt1[cnt]++;//等于就说明情况数,直接++
            if(x == y)break;
        }
    }
}
int m;
int n;
int main(){
    memset(val,0x3f,sizeof(val));//初始化
    memset(sum,0x3f,sizeof(val));
    ios::sync_with_stdio(false);//cin/cout优化
    cin.tie(0);//同上一行,不用cin/cout的不用写这东西
    cin>>n;
    for(int i=1;i<=n;++i){
        cin>>val[i];
    }
    cin>>m;
    for(int i=1;i<=m;++i){
        int x,y;
        cin>>x>>y;
        Add(x,y);
    }
    for(int i=1;i<=n;++i){
        if(!dfn[i])Tarjan(i);
    }
    int ans1=1;
    for(int i=1;i<=cnt;++i){//统计答案
        ans1*=cnt1[i];//乘法计数原理统计方案数
        ans+=sum[i];//统计最小花费
    }
    cout<<ans<<" "<<ans1<<"\n";

}
posted @ 2020-06-28 21:09  Vocanda  阅读(154)  评论(0编辑  收藏  举报