HDU 4966 GGS-DDU 最小树形图

题意:一共有N门课,每门课有不同的等级。一个人初始的所有课的等级为0,他想让自己的每门课都达到最高的等级。

           给出M个班级。每个班级会对一个课程有最低等级限制,同时完成这个班级的学习,会让一门课达到对应的等级,同时进这个班级会有花费。

           问:这个人能不能每门课都达到最高等级。如果能,求出最小花费。

思路:图论,最小树形图

          我们对每个课程每个等级建一个点。同时有一个虚拟的根节点。因为初始的等级全为0,所以我们从根向每个课程的等级0的节点连一条费用为0 的边。

          不难理解,如果我能到某个课程的一个等级,那我可以认为修完了低于该等级的该课程,其花费为0。这就意味着,对于每个课程,我们从高等级向低等级连费用为0的边。

          而完成这个班级的学习,相当于从最低要求的点向完成学习到达的等级的点连一条费用为money的边。

          而,这个问题就转换成了,从根节点出发,是否全部节点可达,如果可达,求出最小费用。

代码如下:

#include<cstdio>
#include<map>
#include<queue>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<list>
#include<set>
#include<cmath>
using namespace std;
const int maxn = 600 + 5;
const int INF = 1e9;
const double eps = 1e-6;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int, int> P;
#define fi first
#define se second

// 固定根的最小树型图,邻接矩阵写法,时间复杂度O(n^3)
struct MDST {
  int n;
  int w[maxn][maxn]; // 边权
  int vis[maxn];     // 访问标记,仅用来判断无解
  int ans;           // 计算答案
  int removed[maxn]; // 每个点是否被删除
  int cid[maxn];     // 所在圈编号
  int pre[maxn];     // 最小入边的起点
  int iw[maxn];      // 最小入边的权值
  int max_cid;       // 最大圈编号

  void init(int n) {
    this->n = n;
    for(int i = 0; i < n; i++)
      for(int j = 0; j < n; j++) w[i][j] = INF;
  }

  void AddEdge(int u, int v, int cost) {
    w[u][v] = min(w[u][v], cost); // 重边取权最小的
  }

  // 从s出发能到达多少个结点
  int dfs(int s) {
    vis[s] = 1;
    int ans = 1;
    for(int i = 0; i < n; i++)
      if(!vis[i] && w[s][i] < INF) ans += dfs(i);
    return ans;
  }

  // 从u出发沿着pre指针找圈
  bool cycle(int u) {
    max_cid++;
    int v = u;
    while(cid[v] != max_cid) { cid[v] = max_cid; v = pre[v]; }
    return v == u;
  }

  // 计算u的最小入弧,入弧起点不得在圈c中
  void update(int u) {
    iw[u] = INF;
    for(int i = 0; i < n; i++)
      if(!removed[i] && w[i][u] < iw[u]) {
        iw[u] = w[i][u];
        pre[u] = i;
      }
  }

  // 根结点为s,如果失败则返回false
  bool solve(int s) {
    memset(vis, 0, sizeof(vis));
    if(dfs(s) != n) return false;

    memset(removed, 0, sizeof(removed));
    memset(cid, 0, sizeof(cid));
    for(int u = 0; u < n; u++) update(u);
    pre[s] = s; iw[s] = 0; // 根结点特殊处理
    ans = max_cid = 0;
    for(;;) {
      bool have_cycle = false;
      for(int u = 0; u < n; u++) if(u != s && !removed[u] && cycle(u)){
        have_cycle = true;
        // 以下代码缩圈,圈上除了u之外的结点均删除
        int v = u;
        do {
          if(v != u) removed[v] = 1;
          ans += iw[v];
          // 对于圈外点i,把边i->v改成i->u(并调整权值);v->i改为u->i
          // 注意圈上可能还有一个v'使得i->v'或者v'->i存在,因此只保留权值最小的i->u和u->i
          for(int i = 0; i < n; i++) if(cid[i] != cid[u] && !removed[i]) {
            if(w[i][v] < INF) w[i][u] = min(w[i][u], w[i][v]-iw[v]);
            w[u][i] = min(w[u][i], w[v][i]);
            if(pre[i] == v) pre[i] = u;
          }
          v = pre[v];
        } while(v != u);
        update(u);
        break;
      }
      if(!have_cycle) break;
    }
    for(int i = 0; i < n; i++)
      if(!removed[i]) ans += iw[i];
    return true;
  }
};

MDST solver;

int a[maxn], id[maxn];

int main(){
    int n, m;
    while(scanf("%d%d", &n, &m)){
        if(n == 0 && m == 0)
            break;
        id[1] = 0;
        int total = 0;
        for(int i = 1;i <= n;i++){
            scanf("%d", &a[i]);
            id[i+1] = id[i]+a[i]+1;
            total += a[i]+1;
        }
        solver.init(total+1);
        int root = 0;
        for(int i = 1;i <= n;i++){
            solver.AddEdge(0, id[i]+1, 0);
            for(int j = 1;j <= a[i];j++){
                solver.AddEdge(id[i]+j+1, id[i]+j, 0);
            }
        }
        while(m--){
            int c, l1, d, l2, cost;
            scanf("%d%d%d%d%d", &c, &l1, &d, &l2, &cost);
            solver.AddEdge(id[c]+l1+1, id[d]+l2+1, cost);
        }

        if(solver.solve(root))
            printf("%d\n", solver.ans);
        else
            printf("-1\n");
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值