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