【HDU 3072】【JZOJ 4686】 通讯

Description

SERN共有N个部门(总部编号为0),通讯网络有M条单向通讯线路,每条线路有一个固定的通讯花费Ci。
为了保密,消息的传递只能按照固定的方式进行:从一个已知消息的部门向另一个与它有线路的部门传递(可能存在多条通信线路)。我们定义总费用为所有部门传递消息的费用和。
幸运的是,如果两个部门可以直接或间接地相互传递消息(即能按照上述方法将信息由X传递到Y,同时能由Y传递到X),我们就可以忽略它们之间的花费。
由于资金问题(预算都花在粒子对撞机上了),SERN总部的工程师希望知道,达到目标的最小花费是多少。
对于100%的数据,N ≤ 50000 ,M ≤ 10^5 ,Ci ≤ 10^5 ,数据组数 ≤ 5

Analysis

显然什么忽略花费的其实就是一些点处于一个强连通分量内。
那么可以tarjan缩点。
缩点之后大概成了一个DAG上的类MST问题。
我和howarli想到了一个既不能理性证明(意思是可以感性证明)也不能证伪的方法。
类克鲁斯卡尔算法,排序然后把并查集变成一个标记数组。
每连一条有向边就将连到的那个点标记,以后不能再有边连到那里去。(具体看代码)

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define efo(i,v) for(int i=last[v];i;i=next[i])
using namespace std;
typedef long long ll;
const int N=50010,M=200010;
int n,m,num,tot,to[M],next[M],wei[M],last[N];
int now,top,sta[N],dfn[N],low[N],col[N];
bool vis[N],bz[N];
struct edge
{
    int u,v,w;
}a[M];
bool cmp(edge a,edge b)
{
    return a.u<b.u || a.u==b.u && a.v<b.v ||
    a.u==b.u && a.v==b.v && a.w<b.w;
}
bool cmp2(edge a,edge b)
{
    return a.w<b.w;
}
void link(int u,int v,int w)
{
    to[++tot]=v,wei[tot]=w,next[tot]=last[u],last[u]=tot;
}
void tarjan(int v)
{
    sta[++top]=v;
    low[v]=dfn[v]=++now;
    vis[v]=1;
    efo(i,v)
    {
        int u=to[i];
        if(!dfn[u])
        {
            tarjan(u);
            low[v]=min(low[v],low[u]);
        }
        else
        if(vis[u]) low[v]=min(low[v],dfn[u]);
    }
    if(low[v]==dfn[v])
    {
        num++;
        for(;sta[top+1]!=v;col[sta[top]]=num,vis[sta[top--]]=0);
    }
}
int main()
{
    for(scanf("%d %d",&n,&m);n || m;scanf("%d %d",&n,&m))
    {
        now=top=num=tot=0;
        memset(last,0,sizeof(last));
        fo(i,1,m)
        {
            scanf("%d %d %d",&a[i].u,&a[i].v,&a[i].w);
            a[i].u++,a[i].v++;
        }
        sort(a+1,a+m+1,cmp);
        fo(i,1,m)
            if(a[i].u!=a[i-1].u || a[i].v!=a[i-1].v) link(a[i].u,a[i].v,a[i].w);
        memset(vis,0,sizeof(vis));
        memset(dfn,0,sizeof(dfn));
        memset(low,0,sizeof(low));
        memset(sta,0,sizeof(sta));
        tarjan(1);
        fo(i,1,m) a[i].u=col[a[i].u],a[i].v=col[a[i].v];
        sort(a+1,a+m+1,cmp2);
        int k=0;
        ll ans=0;
        memset(bz,0,sizeof(bz));
        fo(i,1,m)
        {
            int x=a[i].u,y=a[i].v,w=a[i].w;
            if(x==y || bz[y]) continue;
            bz[y]=1;
            ans+=w;
            if(k>=num-1) break;
        }
        printf("%lld\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值