【HDU3072】【JZOJ4686】通讯

15 篇文章 0 订阅
5 篇文章 0 订阅

Description

“这一切都是命运石之门的选择。”
试图研制时间机器的机关SERN截获了中二科学家伦太郎发往过去的一条短信,并由此得知了伦太郎制作出了电话微波炉(仮)。
为了掌握时间机器的技术,SERN总部必须尽快将这个消息通过地下秘密通讯网络,传达到所有分部。
SERN共有N个部门(总部编号为0),通讯网络有M条单向通讯线路,每条线路有一个固定的通讯花费Ci。
为了保密,消息的传递只能按照固定的方式进行:从一个已知消息的部门向另一个与它有线路的部门传递(可能存在多条通信线路)。我们定义总费用为所有部门传递消息的费用和。
幸运的是,如果两个部门可以直接或间接地相互传递消息(即能按照上述方法将信息由X传递到Y,同时能由Y传递到X),我们就可以忽略它们之间的花费。
由于资金问题(预算都花在粒子对撞机上了),SERN总部的工程师希望知道,达到目标的最小花费是多少。

Solution

这题两个部门之间传递消息如果能免费,那么他们肯定是能互相连通的。

对于整个图,那些是可以互相连通的?我们知道,强连通分量内的元素可以互相到达,那么解决这题的关键,就是找出所有强连通块,这点Tarjan可以解决。

于是,所有在强连通块的边都是没有贡献的。

这时,我们可以将强连通块缩成一个点,于是,原图变成了一个有向无环图。

接下来,我们要选取总边权和最小的边覆盖所有点(也就是找类似树的一个东西)。

由于保证有解,贪心的想,除起点外,每个点选择入边最小的方案是最优的。

Tips:
这里的缩点只是一个含义上的,我们给每个点打上所在强连通块的标记,做完后枚举每条边,如果边连接的两点标记不同,那么这条边在新图中必须存在,那么更新答案即可。

Code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define fo(i,j,k) for(int i=j;i<=k;i++)
#define fd(i,j,k) for(int i=j;i>=k;i--)
#define N 50001
#define M 100001
#define ll long long
using namespace std;
int dfn[N],low[N];
int dep=0,top=0;
int st[N];
int to[M],next[M],last[M],val[M],num=0;
int c[N],tot=0;
bool bz[N];
int f[N];
void link(int x,int y,int c)
{
    num++;
    to[num]=y;
    next[num]=last[x];
    last[x]=num;
    val[num]=c;
}
void tarjan(int x)
{
    low[x]=dfn[x]=++dep;
    st[++top]=x;
    bz[x]=true;
    for(int i=last[x];i;i=next[i])
    {
        int v=to[i];
        if(!dfn[v])
        {
            tarjan(v);
            low[x]=min(low[x],low[v]);
        }
        else if(bz[v]) low[x]=min(low[x],dfn[v]);
    }
    if(low[x]==dfn[x])
    {
        tot++;
        while(st[top+1]!=x && top)
        {
            c[st[top]]=tot;
            bz[st[top]]=false;
            top--;
        }
    }
}
int main()
{
    int n,m;
    scanf("%d %d",&n,&m);
    while(n && m)
    {
        memset(last,0,sizeof(last));
        memset(next,0,sizeof(next));
        memset(to,0,sizeof(to));
        memset(val,0,sizeof(val));
        num=0;
        fo(i,1,m)
        {
            int u,v,w;
            scanf("%d %d %d",&u,&v,&w);
            link(u,v,w);
        }
        memset(bz,0,sizeof(bz));
        dep=top=0;
        tot=0;
        memset(dfn,0,sizeof(dfn));
        memset(low,0,sizeof(low));
        memset(c,0,sizeof(c));
        memset(st,0,sizeof(st));
        tarjan(0);
        int tt=0;
        ll ans=0;
        memset(f,60,sizeof(f));
        fo(i,0,n-1)
        for(int j=last[i];j;j=next[j])
        {
            int v=to[j];
            if(c[i]!=c[v])
            f[c[v]]=min(f[c[v]],val[j]);
        }
        fo(i,1,tot-1)
        if(i!=c[0]) ans+=f[i];
        printf("%lld\n",ans);
        scanf("%d %d",&n,&m);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值