神犇题解:http://blog.csdn.net/popoqqq/article/details/43024221
题意:给定一个DAG,1为起始点,任意一个点可以直接回到1,每条边有经过代价,求一种最优方案使得每条边至少经过一次,代价最小。
无源汇有下界最小费用可行流。
考虑我们对于无源汇的建图:对于每个点,它的入度下界为容量,S向它拉一条边,出度下界为容量,其向T连一条边,跑一边最大流即可。
这里我们建图实际上一样,但是加上了费用,具体建图如下:
对于x->y,费用为z
S向y建容量为1,费用为z的边(对应无源汇里的入度处理)
x向y建容量INF,费用为z的边(即自由流)
对于每一个点x,假设其出度为a
x向T建容量为a,费用为0的边(出度处理)(注意这里不要加上费用,因为已经在入度处理的时候就加过了)
x向1建容量INF,费用为0的边(对应题意)
然后跑最小费用流就可以了。
膜拜ihopenot大爷。
实际上有一个很大的建图优化,也是无源汇建图的优化,即合并入出度并且相互抵消。但是对于这道题就不好做,因为有费用,不过可以这么想:既然每一条边都要走,我们就直接把费用抽出来,即答案一开始就为所有边走一次的代价,然后这样入度的费用就变为0了。然后再与出度进行抵消,最后边的数量大大减少。
实践证明,从原来改到现在,时间从8000多优化到100多ms。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define N 50005 4 #define INF 1e9 5 inline int read(){ 6 int x=0,f=1;char a=getchar(); 7 while(a<'0' || a>'9') {if(a=='-') f=-1; a=getchar();} 8 while(a>='0' && a<='9') x=x*10+a-'0',a=getchar(); 9 return x*f; 10 } 11 int ans,n,cnt,a[N],p[N],d[N],head[N],S,T; 12 bool vis[N]; 13 queue<int>q; 14 struct edges{ 15 int fr,to,cap,flow,cost,next; 16 }e[2*N]; 17 inline void insert(int u,int v,int f,int c){ 18 e[cnt]=(edges){u,v,f,0,c,head[u]};head[u]=cnt++; 19 e[cnt]=(edges){v,u,0,0,-c,head[v]};head[v]=cnt++; 20 } 21 inline bool spfa(){ 22 memset(d,0x3f,sizeof(d)); 23 d[S]=0; a[S]=INF; q.push(S); 24 while(!q.empty()){ 25 int x=q.front(); q.pop(); vis[x]=0; 26 for(int i=head[x];i>=0;i=e[i].next) 27 if(d[e[i].to]>d[x]+e[i].cost && e[i].flow<e[i].cap){ 28 d[e[i].to]=d[x]+e[i].cost; p[e[i].to]=i; 29 a[e[i].to]=min(a[x],e[i].cap-e[i].flow); 30 if(!vis[e[i].to]) vis[e[i].to]=1,q.push(e[i].to); 31 } 32 } 33 return d[T]<INF; 34 } 35 inline void mincf(){ 36 ans+=a[T]*d[T]; 37 int u=T; 38 while(u!=S){ 39 e[p[u]].flow+=a[T]; 40 e[p[u]^1].flow-=a[T]; 41 u=e[p[u]].fr; 42 } 43 } 44 int main(){ 45 memset(head,-1,sizeof(head)); 46 n=read(); S=0; T=n+1; 47 for(int a,i=1;i<=n;i++){ 48 a=read(); insert(i,T,a,0); 49 if(i!=1) insert(i,1,INF,0); 50 for(int to,co,j=1;j<=a;j++) 51 to=read(),co=read(),insert(i,to,INF,co),insert(S,to,1,co); 52 } 53 while(spfa()) mincf(); 54 printf("%d\n",ans); 55 return 0; 56 }
优化建图:
1 for(int a,i=1;i<=n;i++){ 2 a=read(); cd[i]=a; 3 if(i!=1) insert(i,1,INF,0); 4 for(int to,co,j=1;j<=a;j++) 5 to=read(),co=read(),ans+=co,r[to]++,insert(i,to,INF,co); 6 } 7 for(int i=2;i<=n;i++) 8 if(r[i]>cd[i]) insert(S,i,r[i]-cd[i],0); 9 else insert(i,T,cd[i]-r[i],0);