#HDU 1827 (tarjan + 简单原理)

Problem Description

To see a World in a Grain of Sand
And a Heaven in a Wild Flower,
Hold Infinity in the palm of your hand
And Eternity in an hour.
                  —— William Blake

听说lcy帮大家预定了新马泰7日游,Wiskey真是高兴的夜不能寐啊,他想着得快点把这消息告诉大家,虽然他手上有所有人的联系方式,但是一个一个联系过去实在太耗时间和电话费了。他知道其他人也有一些别人的联系方式,这样他可以通知其他人,再让其他人帮忙通知一下别人。你能帮Wiskey计算出至少要通知多少人,至少得花多少电话费就能让所有人都被通知到吗?

Input

多组测试数组,以EOF结束。
第一行两个整数N和M(1<=N<=1000, 1<=M<=2000),表示人数和联系对数。
接下一行有N个整数,表示Wiskey联系第i个人的电话费用。
接着有M行,每行有两个整数X,Y,表示X能联系到Y,但是不表示Y也能联系X。

Output

输出最小联系人数和最小花费。
每个CASE输出答案一行。

Sample Input

 

12 16 2 2 2 2 2 2 2 2 2 2 2 2 1 3 3 2 2 1 3 4 2 4 3 5 5 4 4 6 6 4 7 4 7 12 7 8 8 7 8 9 10 9 11 10

Sample Output

 

3 6

 题目大意 : 输入一个有向图, n 个顶点, m 条边, 并赋予每个顶点一个权值,表示给这个人打电话要 X 块钱, 再输入m条边, 表示 U 可以打电话给 V,问你最少要通知多少人和最少要花多少钱可以把所有人都通知到 (别人打电话不算你的费用哦)

如图所示 :

 假如这是一张已经缩点后的图, tarjan的题不是特别难的话一般都和入度出度有关,所以先看看入度出度方面有什么助于解题的地方。观察后可以发现,被通知也就是表明这个人有入度,那么想要所有人都被通知,是不是就得让入度为0的人由你来通知呢?所以,答案已经出来了,我们只要找到所有入度为0的点,(该图是1 和 4), 然后找到这个缩点后点集里所有点的权值最小的那个,再相加就OK了!

AC代码 :

#include<cstdio>
#include<iostream>
#include<cstring>
#include<stack>
using namespace std;
const int maxn = 2e3 + 5;

struct node
{
    int v, next;
}e[maxn];
int dfn[maxn], low[maxn], suo[maxn], tot, cnt, scnt;
int head[maxn], val[maxn], in[maxn], n, m, index;
bool vis[maxn];
stack <int> st;
void init() {
    memset(e, 0, sizeof(e));
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(suo, 0, sizeof(suo));
    memset(val, 0, sizeof(val));
    memset(head, -1, sizeof(head));
    memset(in, 0, sizeof(in));
    tot = cnt = scnt = 0;
}
void add (int from, int to) {
   e[++cnt].v = to;
   e[cnt].next = head[from];
   head[from] = cnt;
}
void tarjan (int x) {
   dfn[x] = low[x] = ++tot;
   vis[x] = 1;
   st.push(x);
   for (int i = head[x]; i != -1; i = e[i].next) {
        if (!dfn[e[i].v]) {
            tarjan (e[i].v);
            low[x] = min (low[x], low[e[i].v]);
        }
        else if (vis[e[i].v]) low[x] = min (low[x], dfn[e[i].v]);
   }
   if (low[x] == dfn[x]) {
        scnt++;
        int k;
        do {
            k = st.top();
            st.pop();
            vis[k] = 0;
            suo[k] = scnt;
        }
        while (k != x);
   }
}

int main()
{
    while (scanf("%d%d", &n, &m) != EOF) {
        init();
        for (int i = 1; i <= n ;i++) cin >> val[i];
        for (int i = 0; i < m; i++) {
            int ui, vi;
            scanf("%d%d", &ui, &vi);
            add (ui, vi);
        }
        for (int i = 1; i <= n; i++) {
            if (!dfn[i]) tarjan (i);
        }
        for (int i = 1; i <= n; i++) {
            for (int j = head[i]; j != -1; j = e[j].next) {
                int u = suo[i];
                int v =suo[e[j].v];
                if (u != v) in[v]++;
            }
        }
        int min_ = 0, sum = 0;
        for (int i = 1; i <= scnt; i++) {
            if (!in[i]) {
                sum++;  // 表示要通知的人数
                int ans = 1e9;
                for (int j = 1; j <= n; j++) {
                    if (suo[j] == i) ans = min (ans, val[j]);  //找到最小的
                }
                min_ += ans;
            }
        }
        cout << sum << " " << min_ << endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43851525/article/details/91447639