1 条题解
-
0
自动搬运
来自洛谷,原作者为

Orion545
**搬运于
2025-08-24 21:55:52,当前版本为作者最后更新于2018-04-06 23:10:40,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
广告
正文
转化模型:给一张有向无环图,每次你可以选择一条路径走,花费的时间为路径上边权的总和,问要使所有边都被走至少一遍(可以重复),至少需要花费多久
走至少一遍,等价于覆盖这条边
也就是说,我们要找这个图的一个可重复的路径覆盖
路径覆盖让我们想到什么算法了呢?
网络流啊!
我们考虑建立网络流图模型。
这道题里面有个关键:走过一条边,走几次就要花几次的费用,这想到什么?
费用流嘛!
我们定义走一次路径会给这条路径上的所有边增加1的流量,再给所有边赋一个费用等于边权
这样,我们只要设每条边的流量有一个1的下限,上限为无限大,就能做了
还要把所有的剧情结束点(没有出边的)连到一个超级汇点,源点就是1号点
跑一个最小费用可行流即可
这里附上最小费用可行流的教程
最小费用可行流
考虑一张网络流图,每条边定义为(u,v,w,l,r),代表从u到v的一条有向边,费用为w,容量为[l,r]闭区间,源点s汇点t已知,且保证源点没有入边、汇点没有出边
同时定义常规费用流图的边为(u,v,w,cap)
现在我们需要求这张图的最小费用可行流(就是满足所有边的流量上下限制,同时费用最小)
按照如下方式建立附加边和附加点:
1.建立附加源点SS,和附加汇点TT
2.对于原图中每一个点(包括源汇)u,令d[u]代表u点的所有入边的流量下界减去出边的流量下界
2.1.如果d[u]是负数,那么从u连一条边(u,TT,0,-d[u])到TT
2.2.如果d[u]是正数,那么从SS连一条边(SS,u,0,d[u])到u
3.对于原图中每一条边(u,v,w,l,r),连边(u,v,w,r-l)
4.连边(t,s,0,inf)(注意这里是原图的源汇点!不是附加的源汇点!!)
这样以后,从SS到TT跑新图的最小费用最大流,再加上原图中每条边的下界流量乘以费用(必须跑的部分),就是最小费用可行流的费用了
为什么?
我们考虑一个点,流入边流量下界比流出边流量下界大1,即d[u]==1
此时,我们要有一个“补流”的思想
此时出小于入,那么出边的流量下界就会比入边的小1
因为下界一定是要满的,而我们如果希望消除下界影响,新图中的旧图的边,流量上届一定是(r-l)
那我们势必要找一个方法,令这个比较小的流量流出下界,能与比较大的流量流入下界“平起平坐”
这个时候,假如我们从超级源补1的流量过来,那是不是相当于“帮了”输出边一把,平衡了一下“实力强大”的输入边呢?
这样我们就完成了补流过程
上面那段是感性理解,如果想看证明的话,可以看看神犇的博客
Code:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> int inf=1e9+7; using namespace std; inline int read(){ int re=0,flag=1;char ch=getchar(); while(ch>'9'||ch<'0'){ if(ch=='-') flag=-1; ch=getchar(); } while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar(); return re*flag; } int n,ans,first[510],cnt=-1; int dis[510],vis[510],q[20010],head,tail; struct edge{ int to,next,w,cap; }a[100010]; inline void add(int u,int v,int w,int cap){ a[++cnt]=(edge){v,first[u],w,cap};first[u]=cnt; a[++cnt]=(edge){u,first[v],-w,0};first[v]=cnt; } bool spfa(int s,int t){ int i,u,v,w;head=0,tail=1; memset(dis,-1,sizeof(dis));memset(vis,0,sizeof(vis)); q[0]=t;dis[t]=0;vis[t]=1; while(head<tail){ u=q[head++];vis[u]=0; for(i=first[u];~i;i=a[i].next){ if(!a[i^1].cap) continue; v=a[i].to;w=a[i].w; if(dis[v]==-1||dis[v]>dis[u]-w){ dis[v]=dis[u]-w; if(!vis[v]){ vis[v]=1,q[tail++]=v; } } } } return ~dis[s]; } int dfs(int u,int t,int limit){ if(u==t){vis[t]=1;return limit;} int i,v,f,flow=0;vis[u]=1; for(i=first[u];~i;i=a[i].next){ v=a[i].to; if((dis[v]==dis[u]-a[i].w)&&(a[i].cap)&&(!vis[v])){ f=dfs(v,t,min(limit,a[i].cap)); if(f){ flow+=f;limit-=f; ans+=f*a[i].w; a[i].cap-=f;a[i^1].cap+=f; if(!limit) return flow; } } } return flow; } int costflow(int s,int t){//我写的是从某博客上学的改进版zkw费用流 int re=0; while(spfa(s,t)){ vis[t]=1; while(vis[t]){ memset(vis,0,sizeof(vis)); re+=dfs(s,t,inf); } } return re; } int d[510]; int main(){ memset(first,-1,sizeof(first)); n=read();int i,t1,t2,t3,j; for(i=1;i<=n;i++){ t1=read(); for(j=1;j<=t1;j++){ t2=read();t3=read(); d[i]--;d[t2]++;ans+=t3;//流量下界其实都是一 add(i,t2,t3,inf); } } for(i=2;i<=n;i++){ add(i,n+1,0,inf); } for(i=1;i<=n;i++){//补流过程 if(d[i]>0) add(0,i,0,d[i]); if(d[i]<0) add(i,n+2,0,-d[i]); } add(n+1,1,0,inf); costflow(0,n+2); cout<<ans<<endl; }
- 1
信息
- ID
- 3013
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者