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