题意
有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 }