一些内容在另一篇博客
有的时候要保证最大的情况下,费用尽可能优。
就要用费用流了。
目前所涉及的费用流,都是在最大流的前提下
所以,当题目可以转化成,在保证。。。的情况下,最优化。。。
也许就可以尝试费用流了。
(同样意味着选择,
最小割没有什么最大流的前提,可以没有什么限制地,割掉一些边即可。
但是,每条边必须割掉,不能“割一部分”,对于一些可以剩下的模型就捉襟见肘了。
)
用EK。因为dinic不能保证流出来的是最优代价的。
至于为什么每次贪心选择最优的路径,最后就是最优的,可能的原因是,因为有反边,所以自带反悔自动机功能。贪心没有问题。
可以延伸出来一个结论:
最短路费用流,当前费用是所有能够到达当前总流量的所有费用中最优的一个。
(伪证:
因为贪心成立。还没有听说过哪个贪心不是局部最优解,却是全局最优解的2333
)
例题:
方格取数、晨跑。
拆点费用流即可。
修车,美食节
[NOI2012]美食节——费用流(带权二分图匹配)+动态加边
拆点费用流。
边权设计:
反过来考虑每个人站在某个位置,对后面的人等待时间产生的影响。
第j个阶段,意味着后面还有j个。这样,贡献就只和自己有关系了。
必要的时候动态加边。
倍数关系,这个倍数关系还是质数。
那么,这两个数的质因子一定相差一,差的那个质因子就是这个质数。
所以,把数按照质因子个数奇偶性分成左右两类
左部点右部点自己之间不会有配对。
左部右部点之间,如果成倍数关系,并且一个是另一个质因子个数+1,那么连边流inf,费c1*c2。
左部点和S,连流b费0
右部点和T,连流b费0
这样,每个流就是一个配对。
至于
“在获得的价值总和不小于 0 的前提下,求最多进行多少次配对。”
费用流的特点是,
“可以保证当前的费用永远是当前流量下最优的”
所以,如果当前总费用<0
那么就可以停止了。因为之后,流量更大的话,费用也不可能更高。
#include<bits/stdc++.h> #define reg register int #define il inline #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;x=0;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=200*2+5; const int M=200*200+200+200; const int U=31622+20; const int inf=0x3f3f3f3f; int n,m,s,t; struct node{ int nxt,to; int w; ll v; }e[2*M]; int hd[N],cnt=1; void add(int x,int y,int z,ll c){ e[++cnt].nxt=hd[x]; e[cnt].to=y; e[cnt].w=z; e[cnt].v=c; hd[x]=cnt; } struct po{ int a,b,c; int cnt; bool friend operator <(po a,po b){ return a.a<b.a; } }a[N]; int pri[M],tot; bool vis[U]; void sieve(){ vis[1]=1; for(reg i=2;i<=U-10;++i){ if(!vis[i]){ pri[++tot]=i; } for(reg j=1;j<=tot;++j){ if((ll)pri[j]*i>U-10) break; vis[pri[j]*i]=1; if(i%pri[j]==0) break; } } } int che(int x){ int ret=0; for(reg i=1;i<=tot&&pri[i]*pri[i]<=x;++i){ if(x%pri[i]==0){ while(x%pri[i]==0) x/=pri[i],++ret; } } if(x!=1) ++ret; return ret; } ll d[N]; int pre[N]; int incf[N]; queue<int>q; ll now,flow; bool in[N]; bool spfa(){ memset(d,0xcf,sizeof d); while(!q.empty()) q.pop(); d[s]=0; q.push(s); pre[s]=0;incf[s]=inf; while(!q.empty()){ int x=q.front();q.pop(); in[x]=0; // cout<<"x "<<x<<endl; for(reg i=hd[x];i;i=e[i].nxt){ int y=e[i].to; // cout<<" yy "<<y<<endl; if(e[i].w){ if(d[y]<d[x]+e[i].v){ d[y]=d[x]+e[i].v; pre[y]=i; incf[y]=min(incf[x],e[i].w); if(!in[y]) { in[y]=1; q.push(y); } } } } } // cout<<" spfa "<<d[t]<<endl; if(d[t]==-3472328296227680305ll) return false; return true; } bool upda(){ //cout<<" d[t] "<<d[t]<<endl; if(now+d[t]*incf[t]<0){ flow+=now/abs(d[t]); return false; } int x=t; while(pre[x]){ //cout<<"upda "<<x<<endl; e[pre[x]].w-=incf[t]; e[pre[x]^1].w+=incf[t]; x=e[pre[x]^1].to; } flow+=incf[t]; now+=d[t]*incf[t]; //cout<<" now "<<now<<" "<<flow<<endl; return true; } int le[N],ri[N]; int lc,rc; int main(){ scanf("%d",&n); for(reg i=1;i<=n;++i){ rd(a[i].a); }for(reg i=1;i<=n;++i) rd(a[i].b); for(reg i=1;i<=n;++i) rd(a[i].c); sieve(); for(reg i=1;i<=n;++i){ a[i].cnt=che(a[i].a); if(a[i].cnt%2==0){ le[++lc]=i; }else ri[++rc]=i; } for(reg i=1;i<=lc;++i){ int id=le[i]; for(reg j=1;j<=rc;++j){ int to=ri[j]; if(abs(a[id].cnt-a[to].cnt)==1){ if(a[id].a%a[to].a==0||a[to].a%a[id].a==0){ add(id,to,inf,(ll)a[id].c*a[to].c); add(to,id,0,-(ll)a[id].c*a[to].c); } } } } s=n+1,t=n+2; for(reg i=1;i<=lc;++i){ int id=le[i]; add(s,id,a[id].b,0); add(id,s,0,0); } for(reg i=1;i<=rc;++i){ int to=ri[i]; add(to,t,a[to].b,0); add(t,to,0,0); } // cout<<" cnt "<<cnt<<endl; while(spfa()){ // cout<<" xx "<<endl; bool fl=upda(); if(!fl) break; } printf("%lld",flow); return 0; } } int main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2018/11/27 15:59:23 */
看似费用流。
其实发现,Bob一定选择最大的流量*p
所以,Alice要使得所选择的最大流的最大流量最小。
二分判断即可。
注意,边最大流量不一定是整数。因为可以均摊成小数变得更低。
eps 1e-5即可。
#include<bits/stdc++.h> #define reg register int #define il inline #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;x=0;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=103; const int M=1000+3; const int inf=1023333333.00; const double eps=1e-8; int n,m,p,s,t; struct bian{ int x,y,c; }b[M]; struct node{ int nxt,to; double w; }e[2*M]; int hd[N],cnt=1; void add(int x,int y,double z){ //cout<<" x y z "<<x<<" "<<y<<" "<<z<<endl; e[++cnt].nxt=hd[x]; e[cnt].to=y; e[cnt].w=z; hd[x]=cnt; } int d[N]; double dfs(int x,double flow){ if(x==t) return flow; double res=flow; for(reg i=hd[x];i&&fabs(res)>=eps;i=e[i].nxt){ int y=e[i].to; if(e[i].w&&d[y]==d[x]+1){ double k=dfs(y,min(res,e[i].w)); if(fabs(k)<eps) d[y]=0; e[i].w-=k; e[i^1].w+=k; res-=k; } } return flow-res; } int q[2*N],l,r; double ans; bool bfs(){ memset(d,0,sizeof d); d[s]=1; l=1,r=0; q[++r]=s; while(l<=r){ int x=q[l++]; //cout<<" xxx "<<x<<endl; for(reg i=hd[x];i;i=e[i].nxt){ int y=e[i].to; //cout<<" yy "<<y<<" "<<e[i].w<<endl; if(fabs(e[i].w)>=eps&&!d[y]){ d[y]=d[x]+1; q[++r]=y; if(y==t) return true; } } } return false; } double dinic(){ double ret=0; double flow=0; while(bfs()){ while((flow=dfs(s,inf))>=eps) ret+=flow; } return ret; } int main(){ scanf("%d%d%d",&n,&m,&p); int x,y,c; s=1,t=n; double R=0.00; for(reg i=1;i<=m;++i){ rd(x);rd(y);rd(c); R=max(R,(double)c); b[i].x=x,b[i].y=y;b[i].c=c; add(x,y,(double)c); add(y,x,0); } int ans=dinic(); printf("%d ",ans); double L=0.00; R+=2.33; double op=0.00; while(R-L>=eps){ double mid=(L+R)/2; memset(hd,0,sizeof hd);cnt=1; for(reg i=1;i<=m;++i){ add(b[i].x,b[i].y,min((double)b[i].c,mid)); add(b[i].y,b[i].x,0); } double now=dinic(); if(now<ans){ L=mid; }else{ R=mid;op=mid; } } printf("%lf",op*p); return 0; } } int main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2018/11/27 17:09:49 */
upda:2019.2.11
由于贪心成立,
所以,费用流的一个优秀的性质是,当前选择的,就是当前的最优解
在一些基于费用流的剪枝中,经常会用到费用单调的性质
即每一次的费用都大于等于上一次
这个可以证明
upda:2019.6.11
无源汇上下界最小费用可行流,消负环?
用循环流填充负环。
法一:spfa时候找到负环,不断找pre,整个负环的流量就是容量最小的边。暴力填满
法二:我
先砍掉下界。但是先不处理下界带来的盈余流。
先处理负循环流
类似上下界网络流,钦定负边直接流满,注意这里可以反悔!也就是反向边有流量!
超级源汇,最小费用最大流,处理每个点关于负边产生的盈余流。然后就把负循环流填满了。
然后,再和上下界可行流一样,
把干掉下界带来的盈余流,超级源超级汇,最小费用最大流处理掉。
至于有源汇的循环流有什么实际意义,,,,我也不清楚
反正在无源汇上下界中,只有循环流了。