hdu 4966 GGS-DDU 最小树形图/有向图的最小生成树

题意:

总共有n门课,每门也有一个最高等级ai,一开始每门都是等级0。接着有m个提升班,每个班级有a l1 b l2 w,表示只有第a门课程在l1及以上时才能上,花费代价w,可以讲第b门课程提升到l2等级。现在就将所有的课程提升到最高等级需要付出的最小代价,若无法达到则输出-1。

题解:

所有课程的每个等级视为一个节点,对于每门课程的等级i,可以建一条对等级i-1的有向边,边权为0;对于每个提升班,可以建一条边权为w的从点(a,l1)到(b,l2)的有向边。那么根据题意,就是求这幅图的最小生成树。最小生成树的所有边的权值之和就是答案了。

最小树形图的简要步骤:

1)去掉所有自环,判断是否可以成树。

2)找出所有点的最小入边。

3)找出所有最小入边组成的环,之后缩点,将环视为一个新的结点建图,指向该环点v的边(u,v),其权值变为w-in[v],in[v]为v的最小入边。

4)继续2)步骤直到不存在环,其结果就是所有环内的入边权值之和,加上缩点之后新增最小的入边。

复杂度O(VE)

代码:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <cctype>
using namespace std;

const int INF=1<<30;
const int maxn=500+50;
const int maxm=2000+10;
struct edge{
    int u,v,w;
    edge(int u=0,int v=0,int w=0):u(u),v(v),w(w){}
}e[maxn+maxm];
int sum[maxn],a[maxn],tot,in[maxn],pre[maxn],id[maxn],vis[maxn];
void add(int u,int v,int w)
{
    e[tot++]=edge(u,v,w);
}
int Directed_MST(int root,int numv,int nume)//建有向图的最小生成树,其所有边的权值和酒是答案,复杂度O(VE)
{
    int i,j,k,u,v,ans=0;
    while(true)
    {
        for(i=0;i<numv;i++)in[i]=INF;
        for(i=0;i<nume;i++)
        {
            u=e[i].u;
            v=e[i].v;
            if(e[i].w<in[v]&&u!=v)
            {
                pre[v]=u;
                in[v]=e[i].w;
            }
        }
        for(i=0;i<numv;i++)
        {
            if(i==root)continue;
            if(in[i]==INF)return -1;//无法成树
        }
        //找环,合成一个新的顶点
        int t=0;
        memset(id,-1,sizeof(id));
        memset(vis,-1,sizeof(vis));
        in[root]=0;
        //标记每个环
        for(i=0;i<numv;i++)
        {
            ans+=in[i];
            v=i;
            while(vis[v]!=i&&id[v]==-1&&v!=root)
            {
                vis[v]=i;
                v=pre[v];
            }
            if(v!=root&&id[v]==-1)//存在环,标记相同的id
            {
                for(u=pre[v];u!=v;u=pre[u])
                    id[u]=t;
                id[v]=t++;
            }
        }
        if(t==0)break;//无环
        for(i=0;i<numv;i++)
            if(id[i]==-1)id[i]=t++;
        //缩点,重新标记序号
        for(i=0;i<nume;i++)
        {
            v=e[i].v;
            e[i].u=id[e[i].u];
            e[i].v=id[e[i].v];
            if(e[i].u!=e[i].v)
                e[i].w-=in[v];
        }
        numv=t;
        root=id[root];
    }
    return ans;
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n==0&&m==0)break;
        int i,j,k;
        sum[0]=tot=0;
        for(i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
            a[i]++;             //等级从1到a[i]开始
            sum[i+1]=sum[i]+a[i];
        }
        //将所有等级作为一个节点,对于等级i,可以建一条对等级i-1的边,边权为0
        //其中sum[n]为虚拟的跟,指向所有的课程的level0的点。
        for(i=0;i<n;i++)
        {
            for(j=sum[i+1]-1;j>sum[i];j--)add(j,j-1,0);
            add(sum[n],sum[i],0);
        }
        int c,d,l1,l2,money;
        for(i=0;i<m;i++)
        {
            scanf("%d%d%d%d%d",&c,&l1,&d,&l2,&money);
            add(sum[c-1]+l1,sum[d-1]+l2,money);
        }
        printf("%d\n",Directed_MST(sum[n],sum[n]+1,tot));
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值