题意:一共有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;
}