【HDU4966】GGS-DDU

题意

    有n种科目,每个科目都有一个最高的等级a[i]。开始的时候,每个科目的等级都是0。现在要选择一些课程进行学习使得每一个科目都达到最高等级。这里有m节课可供选择。对于每门课给出L1[i],c[i],L2[i],d[i],money[i],要选择这门课要求科目c[i]的等级不小于L[i],可以使科目d[i]的等级升为L2[i],花费金钱money[i]。请计算最小花费是多少。

数据范围N<=50,M<=2000,sum of a[1] to a[n]不超过500.

分析

  最小树形图,要用到朱刘算法。其实。。。我是昨晚在补这道题的时候才去学了一下朱刘算法。。。。。。

  朱刘算法学习博客https://blog.csdn.net/txl199106/article/details/62045479

  对于这道题我是这么想的,把每个科目的每个等级都拆成点,然后建一个编号为0的点向各个科目等级为0的点连一条权值为0的边。对于每个科目,将他们等级从高到低的点依次连权值为0的点,然后对于每一门课程按照要求连边,权值为这门课程的花费。

  建图完成以后,以0点为root跑朱刘算法得到最小树形图,然后这就是答案了。

  应该会这个算法就很好做的一道题。

  

  1 #include <cstdio>
  2 #include <iostream>
  3 #include <algorithm>
  4 #include <cstring>
  5 #include <vector>
  6 using namespace std;
  7 const int maxn=100000+100;
  8 const int INF=2147480000;
  9 
 10 struct Edge{
 11     int from,to;
 12     long long w;
 13 }edges[maxn];
 14 int sz;
 15 int a[100],sum[100];
 16 int N,M;
 17 void add_edge(int from,int to,long long w){
 18     ++sz;
 19     edges[sz].from=from;edges[sz].to=to;edges[sz].w=w;
 20 }
 21 int pre[maxn];//存储父节点
 22 int vis[maxn];//标记作用
 23 int id[maxn];//id[i]记录节点i所在环的编号
 24 int in[maxn];//in[i]记录i入边中最小的权值
 25 long long zhuliu(int root, int n, int m, Edge *edge)//root根 n点数 m边数
 26 {
 27     long long res = 0;
 28     int u, v;
 29     while(1)
 30     {
 31         for(int i = 0; i < n; i++)
 32             in[i] = INF;//初始化
 33         for(int i = 1; i <= m; i++)
 34         {
 35             Edge E = edge[i];
 36             if(E.from != E.to && E.w < in[E.to])
 37             {
 38                 pre[E.to] = E.from;//记录前驱
 39                 in[E.to] = E.w;//更新
 40             }
 41         }
 42         for(int i = 0; i <n; i++)
 43             if(i != root && in[i] == INF)
 44                 return -1;//有其他孤立点 则不存在最小树形图
 45         //找有向环
 46         int tn = 0;//记录当前查找中 环的总数
 47         memset(id, -1, sizeof(id));
 48         memset(vis, -1, sizeof(vis));
 49         in[root] = 0;//
 50         for(int i = 0; i <n; i++)
 51         {
 52             res += in[i];//累加
 53             v = i;
 54             //找图中的有向环 三种情况会终止while循环
 55             //1,直到出现带有同样标记的点说明成环
 56             //2,节点已经属于其他环
 57             //3,遍历到根
 58             while(vis[v] != i && id[v] == -1 && v != root)
 59             {
 60                 vis[v] = i;//标记
 61                 v = pre[v];//一直向上找
 62             }
 63             //因为找到某节点属于其他环  或者 遍历到根  说明当前没有找到有向环
 64             if(v != root && id[v] == -1)//必须上述查找已经找到有向环
 65             {
 66                 for(int u = pre[v]; u != v; u = pre[u])
 67                     id[u] = tn;//记录节点所属的 环编号
 68                 id[v] = tn++;//记录节点所属的 环编号  环编号累加
 69             }
 70         }
 71         if(tn == 0) break;//不存在有向环
 72         //可能存在独立点
 73         for(int i = 0; i <n; i++)
 74             if(id[i] == -1)
 75                 id[i] = tn++;//环数累加
 76         //对有向环缩点  和SCC缩点很像吧
 77         for(int i = 1; i <= m; i++)
 78         {
 79             v = edge[i].to;
 80             edge[i].from = id[edge[i].from];
 81             edge[i].to = id[edge[i].to];
 82             //<u, v>有向边
 83             //两点不在同一个环 u到v的距离为 边权cost - in[v]
 84             if(edge[i].from != edge[i].to)
 85                 edge[i].w -= in[v];//更新边权值 继续下一条边的判定
 86         }
 87         n = tn;//以环总数为下次操作的点数 继续执行上述操作 直到没有环
 88         root = id[root];
 89     }
 90     return res;
 91 }
 92 
 93 int main(){
 94     while(scanf("%d%d",&N,&M)!=EOF&&N&&M){
 95         sz=0;
 96         sum[0]=0;
 97         for(int i=1;i<=N;i++){
 98             scanf("%d",&a[i]);
 99             sum[i]=sum[i-1]+a[i]+1;
100         }
101 
102         int c,L1,d,L2;
103         long long w;
104         for(int i=1;i<=M;i++){
105             scanf("%d%d%d%d%lld",&c,&L1,&d,&L2,&w);
106            /* for(int j=L1;j<=a[c];j++){
107                 add_edge(sum[c-1]+j+1,sum[d-1]+L2+1,w);
108             }*/
109             add_edge(sum[c-1]+L1+1,sum[d-1]+L2+1,w);
110         }
111         for(int i=1;i<=N;i++){
112             add_edge(0,sum[i-1]+1,0);
113         }
114         for(int i=1;i<=N;i++){
115             for(int j=0;j<a[i];j++){
116                 add_edge(sum[i-1]+j+2,sum[i-1]+j+1,0);
117             }
118         }
119         /*printf("%d
",sz);
120         for(int i=1;i<=sz;i++){
121             printf("%d %d %d
",edges[i].from,edges[i].to,edges[i].w);
122         }*/
123         //cout<<sum[n]<<endl;
124         long long ans=zhuliu(0,sum[N]+1,sz,edges);
125         printf("%lld
",ans);
126     }
127 return 0;
128 }
View Code
原文地址:https://www.cnblogs.com/LQLlulu/p/9007562.html