HDU1827:Summer Holiday(缩点 & 贪心)

本文解析了一道关于图论的竞赛题目,通过寻找强连通子图并进行缩点处理,最终确定最少的通知人数和最小的电话费用。适用于对图论和算法竞赛感兴趣的学习者。

Summer Holiday

Time Limit: 10000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 3909    Accepted Submission(s): 1764


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
 

Author
威士忌
 

Source
 

思路:如果这是一个DAG图,威士忌只需联系所有入度为0的点,但是这里有环,我们考虑把它变成无环的DAG,因为有些点集可以看成一个整体,联系其中一个其余都能收到消息,这就是强连通子图,把他们缩成一个点,缩点同时记录“点”内哪个点与威士忌的联系花费最少,缩完后再找入度为0的“点”即可。

# include <iostream>
# include <cstdio>
# include <cstring>
using namespace std;
const int maxn = 1e3+30;
struct node
{
    int u, v, next;
}edge[maxn<<2];
int Next[maxn], belong[maxn], in[maxn], C[maxn], c[maxn], h, tot, cnt, ans;
int dep[maxn], low[maxn], stk[maxn], is_stk[maxn];
void add_edge(int u, int v)
{
    edge[tot] = node{u,v,Next[u]};
    Next[u] = tot++;
}

void init()
{
    tot = cnt = ans = h = 0;
    memset(C, 0x3f, sizeof(C));
    memset(dep, 0, sizeof(dep));
    memset(in, 0, sizeof(in));
    memset(is_stk, 0, sizeof(is_stk));
    memset(low, 0, sizeof(low));
    memset(Next, -1, sizeof(Next));
    memset(belong, 0, sizeof(belong));
}
void dfs(int u)
{
    dep[u] = low[u] = ++h;
    stk[cnt++] = u;
    is_stk[u] = 1;
    for(int i=Next[u]; i!=-1; i=edge[i].next)
    {
        int v = edge[i].v;
        if(!dep[v])
        {
            dfs(v);
            low[u] = min(low[u], low[v]);
        }
        else if(is_stk[v] && dep[v] < low[u])
            low[u] = dep[v];
    }
    if(dep[u] == low[u])
    {
        int j;
        ++ans;
        do
        {
            j = stk[--cnt];
            is_stk[j] = 0;
            belong[j] = ans;
            C[ans] = min(C[ans], c[j]);
        }while(j != u);
    }
}
int main()
{
    int n, m;
    while(~scanf("%d%d",&n,&m))
    {
        init();
        for(int i=1; i<=n; ++i) scanf("%d",&c[i]);
        for(int i=0; i<m; ++i)
        {
            int a, b;
            scanf("%d%d",&a,&b);
            add_edge(a, b);
        }
        for(int i=1; i<=n; ++i)
            if(!dep[i]) dfs(i);
        for(int i=1; i<=n; ++i)
        {
            for(int j=Next[i]; j!=-1; j=edge[j].next)
            {
                int u=edge[j].u, v = edge[j].v;
                if(belong[u] != belong[v])
                ++in[belong[v]];
            }
        }
        int sum = 0, man = 0;
        for(int i=1; i<=ans; ++i)
        {
            if(in[i] == 0)
            {
                ++man;
                sum += C[i];
            }
        }
        printf("%d %d\n",man, sum);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值