【暖*墟】#网络流# 费用流的学习与练习
最小费用最大流
#include <cmath> #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <algorithm> #include <queue> #include <stack> using namespace std; typedef long long ll; typedef unsigned long long ull; #define R register //【p2153】晨跑 [最小费用最大流] // 要求路程最短,天数尽量长。考虑:路程为费用,天数为流量。 //由于每个点只能被访问一次,要进行拆点:将i拆成i1和i2,连边(i1,i2,1,0)(容量为1,费用为0), //对于有向图的每条边(u,v,w),连边(u2,v1,1,w)和其反向边(v1,u2,0,−w)。 void reads(ll &x){ //读入优化(正负整数) ll f=1;x=0;char S=getchar(); while(S<'0'||S>'9'){if(S=='-')f=-1;S=getchar();} while(S>='0'&&S<='9'){x=x*10+S-'0';S=getchar();} x*=f; //正负号 } const ll N=100019; struct edge{ ll ver,nextt,flow,cost; }e[2*N]; ll tot=-1,n,m,S,T,maxf=0,minc=0; ll flow[N],head[N],dist[N],inq[N],pre[N],lastt[N]; void add(ll a,ll b,ll f,ll c) { e[++tot].nextt=head[a],head[a]=tot, e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; } bool spfa(ll S,ll T){ queue<ll> q; memset(inq,0,sizeof(inq)); memset(flow,0x7f,sizeof(flow)); memset(dist,0x7f,sizeof(dist)); q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1; while(!q.empty()){ ll x=q.front(); q.pop(); inq[x]=0; for(ll i=head[x];i!=-1;i=e[i].nextt){ if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){ dist[e[i].ver]=dist[x]+e[i].cost; pre[e[i].ver]=x,lastt[e[i].ver]=i; flow[e[i].ver]=min(flow[x],e[i].flow); if(!inq[e[i].ver]) q.push(e[i].ver),inq[e[i].ver]=1; } } } return pre[T]!=-1; } void mcmf(){ while(spfa(S,T)){ ll now=T; //↓↓最小费用最大流 maxf+=flow[T],minc+=dist[T]*flow[T]; while(now!=S){ //↓↓正边流量-,反边流量+ e[lastt[now]].flow-=flow[T]; e[lastt[now]^1].flow+=flow[T]; //↑↑利用xor1“成对储存”的性质 now=pre[now]; //维护前向边last,前向点pre } } } int main(){ scanf("%lld%lld",&n,&m); memset(head,-1,sizeof(head)); //注意:一定要把head和tot初始化为-1,才能使用xor 1的性质 for(ll i=1,x,y,c;i<=m;i++){ scanf("%lld%lld%lld",&x,&y,&c); add(x+n,y,1,c),add(y,x+n,0,-c); } for(int i=1;i<=n;i++) add(i,i+n,1,0),add(i+n,i,0,0); S=1+n,T=n; //从点1的2号点走到点n的1号点 mcmf(),printf("%lld %lld\n",maxf,minc); //最大流&最小费用 }
#include <cmath> #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <algorithm> #include <queue> #include <stack> using namespace std; typedef long long ll; typedef unsigned long long ull; #define R register /*【p4134】连连看 [最大费用最大流] 给出一个闭区间[a,b],如果区间中的某两个数x,y的平方差x^2-y^2=完全平方数z^2, 并且y与z互质,那么就可以将x和y连起来并且将它们一起消除,同时得到x+y点分数。 那么过关的要求就是,消除的数对尽可能多的前提下,得到足够的分数。*/ //【分析】消除对数尽量多:最大流;得到足够的分数:最大费用。 //根据平方差公式,可以得到(x,y)互质。拆点,建双向边(i->j'和j->i',容量1,费用i+j) // -------------> 注意答案为:maxflow/2和maxcost/2。 //【如何求最大费用最大流?】先将cost取相反数,跑最小费用最大流,再对费用取相反数。 void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; //正负号 } const int N=100019; int gcd(int x,int y){ return (y==0)?x:gcd(y,x%y); } struct edge{ int ver,nextt,flow,cost; }e[2*N]; int tot=-1,n,a,b,S,T,maxf=0,minc=0; int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N]; void add(int a,int b,int f,int c) { e[++tot].nextt=head[a],head[a]=tot, e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; } bool spfa(int S,int T){ queue<int> q; memset(inq,0,sizeof(inq)); memset(flow,0x7f,sizeof(flow)); memset(dist,0x7f,sizeof(dist)); q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1; while(!q.empty()){ int x=q.front(); q.pop(); inq[x]=0; for(int i=head[x];i!=-1;i=e[i].nextt){ if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){ dist[e[i].ver]=dist[x]+e[i].cost; pre[e[i].ver]=x,lastt[e[i].ver]=i; flow[e[i].ver]=min(flow[x],e[i].flow); if(!inq[e[i].ver]) q.push(e[i].ver),inq[e[i].ver]=1; } } } return pre[T]!=-1; } void mcmf(){ while(spfa(S,T)){ int now=T; //↓↓最小费用最大流 maxf+=flow[T],minc+=dist[T]*flow[T]; while(now!=S){ //↓↓正边流量-,反边流量+ e[lastt[now]].flow-=flow[T]; e[lastt[now]^1].flow+=flow[T]; //↑↑利用xor1“成对储存”的性质 now=pre[now]; //维护前向边last,前向点pre } } } int main(){ reads(a),reads(b); n=b-a+1,S=0,T=2*n+1; // T=拆点总数+1 memset(head,-1,sizeof(head)); //记得把head和tot初始化为-1 for(int i=a,z;i<=b;i++) for(int j=a;j<i;j++){ //寻找满足的对数 z=(int)round(sqrt(i*i-j*j)); //(其实没用到xy互质的性质...) if(z*z==i*i-j*j&&gcd(j,z)==1) //↓↓先将cost取相反,跑最小费用最大流 add(i-a+1,j-a+1+n,1,-i-j),add(j-a+1+n,i-a+1,0,-(-i-j)), add(j-a+1,i-a+1+n,1,-i-j),add(i-a+1+n,j-a+1,0,-(-i-j)); } for(int i=1;i<=n;i++) add(S,i,1,0),add(i,S,0,0),add(i+n,T,1,0),add(T,i+n,0,0); mcmf(),printf("%d %d\n",maxf/2,-minc/2); //最大流&最大费用 }
#include <cmath> #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <algorithm> #include <queue> #include <stack> using namespace std; typedef long long ll; typedef unsigned long long ull; #define R register /*【p2053】修车 [最小费用] 同一时刻来了N位车主。有M位技术维修人员,不同人员对不同的车进行维修用时不同。 现在需要安排这M位技术人员所维修的车及顺序,使得顾客平均等待的时间最小。 说明:顾客的等待时间是指从他把车送至维修中心到维修完毕所用的时间。*/ //【分析】把m个工人拆成n*m个,表示n个时间段。和n辆车完全相连(完全二分图)。 // (费用)边的权值=工人时间段编号*输入的时间,表示需要等待的时间。最小费用/n平均。 void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; //正负号 } const int N=1000019; struct edge{ int ver,nextt,flow,cost; }e[2*N]; int tot=-1,n,m,S,T,maxf=0,minc=0,tt[91][19]; int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N]; void add(int a,int b,int f,int c) { e[++tot].nextt=head[a],head[a]=tot, e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; } bool spfa(int S,int T){ queue<int> q; memset(inq,0,sizeof(inq)); memset(flow,0x7f,sizeof(flow)); memset(dist,0x7f,sizeof(dist)); q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1; while(!q.empty()){ int x=q.front(); q.pop(); inq[x]=0; for(int i=head[x];i!=-1;i=e[i].nextt){ if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){ dist[e[i].ver]=dist[x]+e[i].cost; pre[e[i].ver]=x,lastt[e[i].ver]=i; flow[e[i].ver]=min(flow[x],e[i].flow); if(!inq[e[i].ver]) q.push(e[i].ver),inq[e[i].ver]=1; } } } return pre[T]!=-1; } void mcmf(){ while(spfa(S,T)){ int now=T; //↓↓最小费用最大流 maxf+=flow[T],minc+=dist[T]*flow[T]; while(now!=S){ //↓↓正边流量-,反边流量+ e[lastt[now]].flow-=flow[T]; e[lastt[now]^1].flow+=flow[T]; //↑↑利用xor1“成对储存”的性质 now=pre[now]; //维护前向边last,前向点pre } } } int main(){ reads(m),reads(n); S=0,T=n+n*m+1; memset(head,-1,sizeof(head)); for(int i=1;i<=n;i++) add(i+n*m,T,1,0),add(T,i+n*m,0,0); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) //第j个工人的第n个时间段 reads(tt[i][j]),add(S,(i-1)*m+j,1,0),add((i-1)*m+j,S,0,0); //↑↑注意:s和每个点都要连(可能同时出发)[很巧妙的细节处理方法] for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=m;k++) add((j-1)*m+k,i+n*m,1,tt[i][k]*j),add(i+n*m,(j-1)*m+k,0,-(tt[i][k]*j)); //j相当于还剩下的人数,如果在这里花tt[i][k],后面的j个人(包括自己)都会+tt[i][k]; mcmf(),printf("%.2lf\n",(double)minc/n); //平均最小费用(耗时) }
// luogu-judger-enable-o2 #include <cmath> #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <algorithm> #include <queue> #include <stack> using namespace std; typedef long long ll; typedef unsigned long long ull; #define R register /*【p4209】学习小组 [建图困难的最小费用流+条件分析] 共有n个学生,m个学习小组,规定一个学生最多参加k个学习小组。 每个学生参加学习小组要交一定的手续费 /// 学校对学习小组奖励 Ci *a(人数)^2 元。 在参与学生尽量多的情况下,求财务处最少要支出多少钱(若为负数,则输出负数)。*/ /*【分析】由于有Ci*a^2的存在,使得正常加边的费用流无法处理。 每个学习小组向T连:容量为1,费用为Ci*1、Ci*3、Ci*5、Ci*7、...的一堆边(利用平方差关系)。 S向每个学生连容量为k(最多k个小组)、费用为0的边,学生向能参加的学习小组连容量为1、费用为Fi的边。 “在参与学生(而不是每个学习小组的人数总和)尽量多的情况下”指的是所有学生必须有流通过,但不必满流。 所以还要从每个学生向T连一条容量为k-1,费用为0的边,保证费用最小(只参加1个小组)。 */ void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; //正负号 } const int N=500019; struct edge{ int ver,nextt,flow,cost; }e[2*N]; int tot=-1,n,m,k,S,T,maxf=0,minc=0,f[N]; int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N]; void add(int a,int b,int f,int c) { e[++tot].nextt=head[a],head[a]=tot, e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; } bool spfa(int S,int T){ queue<int> q; memset(inq,0,sizeof(inq)); memset(flow,0x7f,sizeof(flow)); memset(dist,0x7f,sizeof(dist)); q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1; while(!q.empty()){ int x=q.front(); q.pop(); inq[x]=0; for(int i=head[x];i!=-1;i=e[i].nextt){ if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){ dist[e[i].ver]=dist[x]+e[i].cost; pre[e[i].ver]=x,lastt[e[i].ver]=i; flow[e[i].ver]=min(flow[x],e[i].flow); if(!inq[e[i].ver]) q.push(e[i].ver),inq[e[i].ver]=1; } } } return pre[T]!=-1; } void mcmf(){ while(spfa(S,T)){ int now=T; //↓↓最小费用最大流 maxf+=flow[T],minc+=dist[T]*flow[T]; while(now!=S){ //↓↓正边流量-,反边流量+ e[lastt[now]].flow-=flow[T]; e[lastt[now]^1].flow+=flow[T]; //↑↑利用xor1“成对储存”的性质 now=pre[now]; //维护前向边last,前向点pre } } } char ss[N]; //每个学生可以加入的小组 int main(){ reads(n),reads(m),reads(k); S=0,T=n+m+1; memset(head,-1,sizeof(head)); for(int i=1,x;i<=m;i++){ reads(x); //小组i(编号i+n)向T连多条费用不同的边 for(int j=1;j<=n;j++) add(i+n,T,1,x*(2*j-1)),add(T,i+n,0,-x*(2*j-1)); } for(int i=1;i<=m;i++) reads(f[i]); //参加小组i的费用 for(int i=1;i<=n;i++){ //↓↓从每个学生向T连边,保证每人都有流的情况下费用最小 add(S,i,k,0),add(i,S,0,0),add(i,T,k-1,0),add(T,i,0,0); scanf("%s",ss+1); for(int j=1;j<=m;j++) if(ss[j]=='1') //每人能报的小组 add(i,j+n,1,-f[j]),add(j+n,i,0,-(-f[j])); //收入用'-'表示 } mcmf(),printf("%d\n",minc); //在有前提条件の建边情况下的最小费用 }
- ↑↑↑ 尴尬至极的写了一篇洛谷博客,结果发现没有AC...
- 分析见:https://www.luogu.org/blog/hss-FH/solution-p4209
#include <cmath> #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <algorithm> #include <queue> #include <stack> using namespace std; typedef long long ll; typedef unsigned long long ull; #define R register /*【p2517】订货 在第i个月对某产品的需求量为Ui,该产品的订货单价为di, 仓库容量为k,上个月月底未销完的产品要付单位存贮费用m, 假定第一月月初的库存量为零,第n月月底的库存量也为零, 问如何安排这n个月订购计划,才能使成本最低?*/ /*【分析】S->i c=inf f=di; i->T c=ui f=0; i->i+1 c=k f=m。 即,如此图:https://cdn.luogu.org/upload/pic/21514.png 建图。 */ void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; //正负号 } const int N=500019,INF=0x3f3f3f3f; struct edge{ int ver,nextt,flow,cost; }e[2*N]; int tot=-1,n,m,k,S,T,maxf=0,minc=0,f[N]; int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N]; void add(int a,int b,int f,int c) { e[++tot].nextt=head[a],head[a]=tot, e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; } bool spfa(int S,int T){ queue<int> q; memset(inq,0,sizeof(inq)); memset(flow,0x7f,sizeof(flow)); memset(dist,0x7f,sizeof(dist)); q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1; while(!q.empty()){ int x=q.front(); q.pop(); inq[x]=0; for(int i=head[x];i!=-1;i=e[i].nextt){ if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){ dist[e[i].ver]=dist[x]+e[i].cost; pre[e[i].ver]=x,lastt[e[i].ver]=i; flow[e[i].ver]=min(flow[x],e[i].flow); if(!inq[e[i].ver]) q.push(e[i].ver),inq[e[i].ver]=1; } } } return pre[T]!=-1; } void mcmf(){ while(spfa(S,T)){ int now=T; //↓↓最小费用最大流 maxf+=flow[T],minc+=dist[T]*flow[T]; while(now!=S){ //↓↓正边流量-,反边流量+ e[lastt[now]].flow-=flow[T]; e[lastt[now]^1].flow+=flow[T]; //↑↑利用xor1“成对储存”的性质 now=pre[now]; //维护前向边last,前向点pre } } } int main(){ reads(n),reads(m),reads(k); S=0,T=n+1; int x; memset(head,-1,sizeof(head)); for(int i=1;i<=n;i++) reads(x),add(i,T,x,0), add(T,i,0,0); // ↑↑ i->T c=ui(需求量) f=0; for(int i=1;i<=n;i++) reads(x),add(S,i,INF,x), add(i,S,0,-x); // ↑↑ S->i c=inf f=di(单位流量费用); for(int i=1;i<n;i++) add(i,i+1,k,m),add(i+1,i,0,-m); //m:单位储存费用 mcmf(),printf("%d\n",minc); //输出最小费用 }
#include <cmath> #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <algorithm> #include <queue> #include <stack> using namespace std; typedef long long ll; typedef unsigned long long ull; #define R register /*【p3980】志愿者招募 [神奇的建图方式] 项目需要N天才能完成,其中第i天至少需要Ai个人。 有M类志愿者,其中第i类可以从第Si天工作到第Ti天,费用Ci元。 希望用尽量少的费用招募足够的志愿者。 */ /*【分析】S连第一天,最后一天连T,这两条边容量为inf,费用为0(即通行无阻)。 每天向后一天连边,c=INF-a[i],f=0,沿时间流依次解决每一天的问题。 将每一类志愿者的s[i]与t[i]+1连一条:c=INF,f=c[i]的边。 */ //可以看 https://www.luogu.org/blog/user7035/solution-p3980 的分析 void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; //正负号 } const int N=500019,INF=0x3f3f3f3f; struct edge{ int ver,nextt,flow,cost; }e[2*N]; int tot=-1,n,m,k,S,T,maxf=0,minc=0,f[N]; int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N]; void add(int a,int b,int f,int c) { e[++tot].nextt=head[a],head[a]=tot, e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; } bool spfa(int S,int T){ queue<int> q; memset(inq,0,sizeof(inq)); memset(flow,0x7f,sizeof(flow)); memset(dist,0x7f,sizeof(dist)); q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1; while(!q.empty()){ int x=q.front(); q.pop(); inq[x]=0; for(int i=head[x];i!=-1;i=e[i].nextt){ if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){ dist[e[i].ver]=dist[x]+e[i].cost; pre[e[i].ver]=x,lastt[e[i].ver]=i; flow[e[i].ver]=min(flow[x],e[i].flow); if(!inq[e[i].ver]) q.push(e[i].ver),inq[e[i].ver]=1; } } } return pre[T]!=-1; } void mcmf(){ while(spfa(S,T)){ int now=T; //↓↓最小费用最大流 maxf+=flow[T],minc+=dist[T]*flow[T]; while(now!=S){ //↓↓正边流量-,反边流量+ e[lastt[now]].flow-=flow[T]; e[lastt[now]^1].flow+=flow[T]; //↑↑利用xor1“成对储存”的性质 now=pre[now]; //维护前向边last,前向点pre } } } int main(){ reads(n),reads(m); S=0,T=n+2; memset(head,-1,sizeof(head)); //↓↓注意,要到n+1 for(int i=1,ai;i<=n;i++) reads(ai),add(i,i+1,INF-ai,0),add(i+1,i,0,0); add(S,1,INF,0),add(1,S,0,0); add(n+1,T,INF,0),add(T,n+1,0,0); for(int i=1,si,ti,ci;i<=m;i++) reads(si),reads(ti),reads(ci), add(si,ti+1,INF,ci),add(ti+1,si,0,-ci); mcmf(),printf("%d\n",minc); //输出最小费用 }
#include <cmath> #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <algorithm> #include <queue> #include <stack> using namespace std; typedef long long ll; typedef unsigned long long ull; #define R register /*【p3159】交换棋子 [0/1网格图费用流] n行m列的黑白棋盘,每次可以交换两个相邻格子(八连通)中的棋子, 要求第i行第j列的格子只能参与mi,j次交换。求达到目标状态的最小交换总次数。 */ /*【分析】感觉GXZdalao真的超强超强的√ 分析思路超级清楚啊啊啊啊啊 成绩还高qwq 我这个蒟蒻就只好来放链接了QAQ https://www.cnblogs.com/GXZlegend/p/8310945.html */ void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; //正负号 } const int N=500019,M=1217,INF=0x3f3f3f3f; struct edge{ int ver,nextt,flow,cost; }e[2*N]; int tot=-1,n,m,k,S,T,maxf=0,minc=0,a[M][M],b[M][M],c[M][M]; int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N]; void add(int a,int b,int f,int c) { e[++tot].nextt=head[a],head[a]=tot,e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; e[++tot].nextt=head[a],head[a]=tot,e[tot].ver=b,e[tot].flow=0,e[tot].cost=-c; } bool spfa(int S,int T){ queue<int> q; memset(inq,0,sizeof(inq)); memset(flow,0x7f,sizeof(flow)); memset(dist,0x7f,sizeof(dist)); q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1; while(!q.empty()){ int x=q.front(); q.pop(); inq[x]=0; for(int i=head[x];i!=-1;i=e[i].nextt){ if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){ dist[e[i].ver]=dist[x]+e[i].cost; pre[e[i].ver]=x,lastt[e[i].ver]=i; flow[e[i].ver]=min(flow[x],e[i].flow); if(!inq[e[i].ver]) q.push(e[i].ver),inq[e[i].ver]=1; } } } return pre[T]!=-1; } void mcmf(){ while(spfa(S,T)){ int now=T; //↓↓最小费用最大流 maxf+=flow[T],minc+=dist[T]*flow[T]; while(now!=S){ //↓↓正边流量-,反边流量+ e[lastt[now]].flow-=flow[T]; e[lastt[now]^1].flow+=flow[T]; //↑↑利用xor1“成对储存”的性质 now=pre[now]; //维护前向边last,前向点pre } } } char ss[1519]; int pos(int i,int j,int k){ return k*n*m+(i-1)*m+j; } //点的编号 int main(){ reads(n),reads(m); S=0,T=3*n*m+1; //每个点拆成三个点(入度、出度、中间点) memset(head,-1,sizeof(head)); int sum1=0,sum2=0; for(int i=1;i<=n;i++){ cin>>ss; for(int j=1;j<=m;j++) a[i][j]=ss[j-1]^'0'; } //始 for(int i=1;i<=n;i++){ cin>>ss; for(int j=1;j<=m;j++) b[i][j]=ss[j-1]^'0'; } //终 for(int i=1;i<=n;i++){ cin>>ss; for(int j=1;j<=m;j++) c[i][j]=ss[j-1]^'0'; } //次数限制 for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ if(!c[i][j]&&a[i][j]!=b[i][j]){ puts("-1"); return 0; } add(pos(i,j,0),pos(i,j,1),(c[i][j]-a[i][j]+b[i][j])>>1,0); //入度 add(pos(i,j,1),pos(i,j,2),(c[i][j]+a[i][j]-b[i][j])>>1,0); //出度 if(a[i][j]) add(S,pos(i,j,1),1,0),sum1++; if(b[i][j]) add(pos(i,j,1),T,1,0),sum2++; //↓↓相邻的点之间互相连边(出度--入度),c=inf,f=1。 if(i>1) add(pos(i,j,2),pos(i-1,j,0),INF,1); if(i<n) add(pos(i,j,2),pos(i+1,j,0),INF,1); if(j>1) add(pos(i,j,2),pos(i,j-1,0),INF,1); if(j<m) add(pos(i,j,2),pos(i,j+1,0),INF,1); if(i>1&&j>1) add(pos(i,j,2),pos(i-1,j-1,0),INF,1); if(i>1&&j<m) add(pos(i,j,2),pos(i-1,j+1,0),INF,1); if(i<n&&j>1) add(pos(i,j,2),pos(i+1,j-1,0),INF,1); if(i<n&&j<m) add(pos(i,j,2),pos(i+1,j+1,0),INF,1); } if(sum1!=sum2){ puts("-1"); return 0; } //黑白棋子数不同,一定无解 mcmf(); if(sum1!=maxf) puts("-1"); else printf("%d\n",minc); }
#include <queue> #include <cstdio> #include <cstring> #define N 1210 #define M 121000 #define inf 1 << 30 #define pos(i , j , k) (k * n * m + (i - 1) * m + j) using namespace std; queue<int> q; int a[25][25] , b[25][25] , c[25][25] , head[N] , to[M] , val[M] , cost[M] , next[M] , cnt = 1 , s , t , dis[N] , from[N] , pre[N]; inline void add(int x , int y , int v , int c) { to[++cnt] = y , val[cnt] = v , cost[cnt] = c , next[cnt] = head[x] , head[x] = cnt; to[++cnt] = x , val[cnt] = 0 , cost[cnt] = -c , next[cnt] = head[y] , head[y] = cnt; } bool spfa() { int x , i; memset(from , -1 , sizeof(from)); memset(dis , 0x3f , sizeof(dis)); dis[s] = 0 , q.push(s); while(!q.empty()) { x = q.front() , q.pop(); for(i = head[x] ; i ; i = next[i]) if(val[i] && dis[to[i]] > dis[x] + cost[i]) dis[to[i]] = dis[x] + cost[i] , from[to[i]] = x , pre[to[i]] = i , q.push(to[i]); } return ~from[t]; } inline int rnum() { char ch = getchar(); while(ch < '0' || ch > '9') ch = getchar(); return ch ^ '0'; } int main() { int n , m , i , j , sum1 = 0 , sum2 = 0 , ans = 0; scanf("%d%d" , &n , &m) , s = 0 , t = 3 * n * m + 1; for(i = 1 ; i <= n ; i ++ ) for(j = 1 ; j <= m ; j ++ ) a[i][j] = rnum(); for(i = 1 ; i <= n ; i ++ ) for(j = 1 ; j <= m ; j ++ ) b[i][j] = rnum(); for(i = 1 ; i <= n ; i ++ ) for(j = 1 ; j <= m ; j ++ ) c[i][j] = rnum(); for(i = 1 ; i <= n ; i ++ ) { for(j = 1 ; j <= m ; j ++ ) { if(!c[i][j] && a[i][j] != b[i][j]) { puts("-1"); return 0; } add(pos(i , j , 0) , pos(i , j , 1) , (c[i][j] - a[i][j] + b[i][j]) >> 1 , 0); add(pos(i , j , 1) , pos(i , j , 2) , (c[i][j] + a[i][j] - b[i][j]) >> 1 , 0); if(a[i][j]) add(s , pos(i , j , 1) , 1 , 0) , sum1 ++ ; if(b[i][j]) add(pos(i , j , 1) , t , 1 , 0) , sum2 ++ ; if(i > 1) add(pos(i , j , 2) , pos(i - 1 , j , 0) , inf , 1); if(i < n) add(pos(i , j , 2) , pos(i + 1 , j , 0) , inf , 1); if(j > 1) add(pos(i , j , 2) , pos(i , j - 1 , 0) , inf , 1); if(j < m) add(pos(i , j , 2) , pos(i , j + 1 , 0) , inf , 1); if(i > 1 && j > 1) add(pos(i , j , 2) , pos(i - 1 , j - 1 , 0) , inf , 1); if(i > 1 && j < m) add(pos(i , j , 2) , pos(i - 1 , j + 1 , 0) , inf , 1); if(i < n && j > 1) add(pos(i , j , 2) , pos(i + 1 , j - 1 , 0) , inf , 1); if(i < n && j < m) add(pos(i , j , 2) , pos(i + 1 , j + 1 , 0) , inf , 1); } } if(sum1 != sum2) puts("-1"); else { while(spfa()) { j = inf; for(i = t ; i != s ; i = from[i]) j = min(j , val[pre[i]]); sum1 -= j , ans += j * dis[t]; for(i = t ; i != s ; i = from[i]) val[pre[i]] -= j , val[pre[i] ^ 1] += j; } if(sum1) puts("-1"); else printf("%d\n" , ans); } return 0; }
#include <cmath> #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <algorithm> #include <queue> #include <stack> using namespace std; typedef long long ll; typedef unsigned long long ull; #define R register /*【p2604】网络扩容 有向图,每条边都有一个容量V和一个扩容费用C(将容量扩大1)。 求:1. 不扩容时1到N的最大流; 2. 将1到N的最大流增加K所需的最小扩容费用。*/ /*【分析】最大流也可以用费用流求出2333 第2问建立超级源点S就行了(有INF的容量即可) */ void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; //正负号 } const int N=500019,INF=0x3f3f3f3f; struct edge{ int ver,nextt,flow,cost; }e[2*N]; int tot=-1,n,m,k,S,T,maxf=0,minc=0,x[N],y[N],v[N],c[N]; int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N]; void add(int a,int b,int f,int c) { e[++tot].nextt=head[a],head[a]=tot, e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; } bool spfa(int S,int T){ queue<int> q; memset(inq,0,sizeof(inq)); memset(flow,0x7f,sizeof(flow)); memset(dist,0x7f,sizeof(dist)); q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1; while(!q.empty()){ int x=q.front(); q.pop(); inq[x]=0; for(int i=head[x];i!=-1;i=e[i].nextt){ if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){ dist[e[i].ver]=dist[x]+e[i].cost; pre[e[i].ver]=x,lastt[e[i].ver]=i; flow[e[i].ver]=min(flow[x],e[i].flow); if(!inq[e[i].ver]) q.push(e[i].ver),inq[e[i].ver]=1; } } } return pre[T]!=-1; } void mcmf(){ while(spfa(S,T)){ int now=T; //↓↓最小费用最大流 maxf+=flow[T],minc+=dist[T]*flow[T]; while(now!=S){ //↓↓正边流量-,反边流量+ e[lastt[now]].flow-=flow[T]; e[lastt[now]^1].flow+=flow[T]; //↑↑利用xor1“成对储存”的性质 now=pre[now]; //维护前向边last,前向点pre } } } int main(){ reads(n),reads(m),reads(k); memset(head,-1,sizeof(head)); for(int i=1;i<=m;i++) reads(x[i]),reads(y[i]), //初始最大流 reads(v[i]),reads(c[i]),add(x[i],y[i],v[i],0),add(y[i],x[i],0,0); S=1,T=n; mcmf(),printf("%d ",maxf); minc=0,maxf=0; //初始,S=1,T=n S=0; add(S,1,k,0),add(1,S,0,0); //超级源点S(求1~N的最大流,所以只用向1连边) for(int i=1;i<=m;i++) add(x[i],y[i],INF,c[i]),add(y[i],x[i],0,-c[i]); mcmf(),printf("%d\n",minc); //输出最小费用 }
#include <cmath> #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <algorithm> #include <queue> #include <stack> using namespace std; typedef long long ll; typedef unsigned long long ull; #define R register //【p3965】循环格 /*【分析】让源点S的流为inf,到每个节点的入点连一条容量1的边。 每个节点的入点 向 他在原图中指向的节点的出点 连一条容量为1的边。 每个节点的出点 向 汇点T 连一条容量为1的边。跑一次最大流。 判断最终的流是否等于n*m(就是原图中的总节点数),则为循环格。*/ /*【深入】最大流只能判定,如何修改?费用流。 */ void reads(int &x){ //读入优化(正负整数) int f=1;x=0;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} x*=f; //正负号 } const int N=10019; struct edge{ int ver,nextt,flow,cost; }e[2*N]; int tot=-1,n,m,S,T,maxf=0,minc=0; int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N]; void add(int a,int b,int f,int c) { e[++tot].nextt=head[a],head[a]=tot, e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; e[++tot].nextt=head[b],head[b]=tot, e[tot].ver=a,e[tot].flow=0,e[tot].cost=-c; } const char dire[4]={'U','R','D','L'}; const int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1}; bool spfa(int S,int T){ queue<int> q; memset(inq,0,sizeof(inq)); memset(flow,0x7f,sizeof(flow)); memset(dist,0x7f,sizeof(dist)); q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1; while(!q.empty()){ int x=q.front(); q.pop(); inq[x]=0; for(int i=head[x];i!=-1;i=e[i].nextt){ if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){ dist[e[i].ver]=dist[x]+e[i].cost; pre[e[i].ver]=x,lastt[e[i].ver]=i; flow[e[i].ver]=min(flow[x],e[i].flow); if(!inq[e[i].ver]) q.push(e[i].ver),inq[e[i].ver]=1; } } } return pre[T]!=-1; } void mcmf(){ while(spfa(S,T)){ int now=T; //↓↓最小费用最大流 maxf+=flow[T],minc+=dist[T]*flow[T]; while(now!=S){ //↓↓正边流量-,反边流量+ e[lastt[now]].flow-=flow[T]; e[lastt[now]^1].flow+=flow[T]; //↑↑利用xor1“成对储存”的性质 now=pre[now]; //维护前向边last,前向点pre } } } void get_c(char &ch){ch=getchar();while(ch<'A'||ch>'Z')ch=getchar();} int main(){ memset(head,-1,sizeof(head)); reads(n),reads(m); S=0,T=N-1; char ch; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ get_c(ch),add(S,(i-1)*m+j,1,0),add(n*m+(i-1)*m+j,T,1,0); for(int k=0;k<4;k++){ int xx=(i+dx[k]+n-1)%n+1,yy=(j+dy[k]+m-1)%m+1; add((i-1)*m+j,n*m+(xx-1)*m+yy,1,ch!=dire[k]); } } mcmf(); cout<<minc<<endl; return 0; }
有上下界的费用流
【p4043】支线剧情:求把所有支线剧情都看完需要的min时间。
【有上下界费用流的建图方法】 1.建立超级源汇S和T。
2.判断是否有要求的源汇,从汇点向源点(此题中为1节点)连容量为inf,费用为0的边。
3.对于原图x->y,下界为low,上界为high,连S->y,容量为low,费用为原费用;
4.同时x->y边容量变为high-low(此题中为inf-1=inf),费用不变。
5.对于每个点x,连x->T,容量为原图中x连向其它点的所有边的low之和(即出度cd[ ]),费用为0。
题目要求每条边都需要走一次,所以每条边下界为1,上界为inf。
然后转化为普通费用流来求。本题中具体建图为:
i[i>1]->1 (inf,0),S->y (1,z),x->y (inf,z),x->T (cd[x],0)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> #include<set> using namespace std; typedef long long ll; //【p4043】支线剧情 //有上下界的费用流 //求:把所有支线剧情都看完需要的min时间。 //果然大家都喜欢玩RPG游戏的吗(我昨天过Shin的单人线都玩到1:30...) //果然所谓的“快进”都是骗人的吗...(过剧情真心痛苦...) /* 有上下界费用流的建图方法: 1.建立超级源汇S和T。 2.判断是否有要求的源汇,从汇点向源点(此题中为1节点)连容量为inf,费用为0的边。 3.对于原图x->y,下界为low,上界为high,连S->y,容量为low,费用为原费用; 4.同时x->y边容量变为high-low(此题中为inf-1=inf),费用不变。 5.对于每个点x,连x->T,容量为原图中x连向其它点的所有边的low之和(即出度cd[]),费用为0。*/ /*【分析】题目要求每条边都需要走一次,所以每条边下界为1,上界为inf。 然后转化为普通费用流来求。本题中具体建图为: i[i>1]->1 (inf,0),S->y (1,z),x->y (inf,z),x->T (cd[x],0)。*/ void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; //正负号 } const int N=100019,inf=0x3f3f3f3f; struct edge{ int ver,nextt,flow,cost; }e[2*N]; int tot=-1,n,S,T,maxf=0,minc=0,f[N]; int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N]; void add(int a,int b,int f,int c) { e[++tot].nextt=head[a],head[a]=tot, e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; e[++tot].nextt=head[b],head[b]=tot, e[tot].ver=a,e[tot].flow=0,e[tot].cost=-c; } bool spfa(int S,int T){ queue<int> q; memset(inq,0,sizeof(inq)); memset(flow,0x7f,sizeof(flow)); memset(dist,0x7f,sizeof(dist)); q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1; while(!q.empty()){ int x=q.front(); q.pop(); inq[x]=0; for(int i=head[x];i!=-1;i=e[i].nextt){ if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){ dist[e[i].ver]=dist[x]+e[i].cost; pre[e[i].ver]=x,lastt[e[i].ver]=i; flow[e[i].ver]=min(flow[x],e[i].flow); if(!inq[e[i].ver]) q.push(e[i].ver),inq[e[i].ver]=1; } } } return pre[T]!=-1; } void mcmf(){ while(spfa(S,T)){ int now=T; //↓↓最小费用最大流 maxf+=flow[T],minc+=dist[T]*flow[T]; while(now!=S){ //↓↓正边流量-,反边流量+ e[lastt[now]].flow-=flow[T]; e[lastt[now]^1].flow+=flow[T]; //↑↑利用xor1“成对储存”的性质 now=pre[now]; //维护前向边last,前向点pre } } } int main(){ reads(n); S=0,T=n+1; memset(head,-1,sizeof(head)); for(int i=1,k,y,z;i<=n;i++) { reads(k); if(k) add(i,T,k,0); if(i!=1) add(i,1,inf,0); while(k--) reads(y),reads(z),add(S,y,1,z),add(i,y,inf,z); } mcmf(); printf("%d\n",minc); return 0; //↑↑注意建图方式 }
// luogu-judger-enable-o2 #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> #include<set> using namespace std; typedef long long ll; //【p4043】80人环游地球 /* 有上下界费用流的建图方法: 1.建立超级源汇S和T。 2.判断是否有要求的源汇,从汇点向源点(此题中为每个i)连容量为inf,费用为0的边。 3.对于原图x->y,下界为low,上界为high,连S->y,容量为low,费用为原费用; 4.同时x->y边容量变为high-low(此题中为inf-1=inf),费用不变。 5.对于每个点x,连x->T,容量为原图中x连向其它点的所有边的low之和(即出度cd[]),费用为0。*/ /*【分析】题目要求每个点需要经过固定的次数vi,可以拆成两个点、之间连一条上下界均为vi的边。 ss=0,tt=2*n+1,tt->ss(m,0)(用于限制流量,即人数); ss->i(inf,0),i+n->tt(inf,0) ; S=2*n+2,T=2*n+3,S->i+n(vi,0),i->T(vi(出度),0) ; i+n->j(inf,cost[i][j])(i<j)*/ void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; //正负号 } const int N=100019,inf=0x3f3f3f3f; struct edge{ int ver,nextt,flow,cost; }e[2*N]; int tot=-1,n,m,S,T,maxf=0,minc=0,f[N]; int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N]; void add(int a,int b,int f,int c) { e[++tot].nextt=head[a],head[a]=tot, e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; e[++tot].nextt=head[b],head[b]=tot, e[tot].ver=a,e[tot].flow=0,e[tot].cost=-c; } bool spfa(int S,int T){ queue<int> q; memset(inq,0,sizeof(inq)); memset(flow,0x7f,sizeof(flow)); memset(dist,0x7f,sizeof(dist)); q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1; while(!q.empty()){ int x=q.front(); q.pop(); inq[x]=0; for(int i=head[x];i!=-1;i=e[i].nextt){ if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){ dist[e[i].ver]=dist[x]+e[i].cost; pre[e[i].ver]=x,lastt[e[i].ver]=i; flow[e[i].ver]=min(flow[x],e[i].flow); if(!inq[e[i].ver]) q.push(e[i].ver),inq[e[i].ver]=1; } } } return pre[T]!=-1; } void mcmf(){ while(spfa(S,T)){ int now=T; //↓↓最小费用最大流 maxf+=flow[T],minc+=dist[T]*flow[T]; while(now!=S){ //↓↓正边流量-,反边流量+ e[lastt[now]].flow-=flow[T]; e[lastt[now]^1].flow+=flow[T]; //↑↑利用xor1“成对储存”的性质 now=pre[now]; //维护前向边last,前向点pre } } } int main(){ reads(n),reads(m); memset(head,-1,sizeof(head)); S=2*n+2,T=2*n+3; add(2*n+1,0,m,0); //ss=0,tt=2*n+1 for(int i=1,x;i<=n;i++) reads(x),add(0,i,inf,0), add(i+n,2*n+1,inf,0),add(S,i+n,x,0),add(i,T,x,0); for(int i=1,x;i<=n;i++) for(int j=i+1;j<=n;j++) { reads(x); if(~x) add(i+n,j,inf,x); } mcmf(); printf("%d\n",minc); return 0; //↑↑注意建图方式 }
——时间划过风的轨迹,那个少年,还在等你