图论相关知识
简单介绍
就我2018年暑假这阵子练过的区域赛题目来看
- 图论题网络流居多,一般是稍难的签到(需要多做点网络流的题目)
- 另外由于DAG的性质,很容易的能够有一些经典的DP,也可以注意一下。
- 其他的主要还是会套模板吧。
一定要理解图论算法的核心思想以及一些规律,比较难的题目(铜牌往上)可能就是这样考 - 其他的题目就见地不多了。可能很难,都做不到。
图论知识以及模板代码
0、前向星
const int N=1e3+10;
const int M=2*N;
int tot,head[N];
void init(){
tot=0;memset(head,-1,sizeof head);
}
struct Edge{
int to,next;
}edge[M];
void addedge(int u,int v){
edge[tot].to=v;edge[tot].next=head[u];
head[u]=tot++;
}
1、拓扑排序
- 注意,DAG才存在topo排序,
bool vis[N];
int ind[N];
int que[N];
void topo(int root){
int q=0,p=0;//队列指针
que[q++]=root;
while(p<q){
int u=que[p++];
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].to;
if(!vis[v]){
ind[v]--;
if(ind[v]==0){
vis[v]=true;
que[q++]=v;
}
}
}
}
}
-
使用DAG的拓扑序求最短路
实际上就是BF的松弛操作,不过由于DAG的性质,可以降低复杂度到m。并且可以处理负边权。
int dist[N];
Rep(i,1,n)dist[i]=INF;
dist[s]=0;
for(int i=0;i<n;i++){
int v;
for(int j=head[que[i]];~j;j=edge[j].next){
v=edge[j].to;
if(dist[v]>dist[u]+edge[j].w)dist[v]=dist[u]+edge[j].w;
}
}
2、生成树
(1)最小生成树
(2)次小生成树
前两个kuangbin 模板有
(3)有向图的最小树形图
-
首先我们来考虑一下DAG的最小树形图
对于每个点来说,连通这个点的最小花费,就是找一条最小的边到这个点。
思考一下DAG和拓扑排序,按照拓扑序来考虑每一个点,其都能通过选择任何一条前驱边使这个点连通。所以我们选择最小的那条边。
所以就使用拓扑序遍历所有遍,然后去更新连通每个点的贡献
-
如果不是DAG的话,除非能缩点,否则只能用下面的板子了。
//来自kuangbin的最小树形图模版:
//UVA - 11183
//最小树形图 求有向图的最小生成树
//复杂度 O(VE)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 1000+10;
const int MAXM = 40000+10;
struct Edge{
//分别为起点,终点,花费
int u,v,cost;
};
Edge edge[MAXM];
//pre[i]表示i节点的入边的起点,in[i]表示该边的权值
int pre[MAXN],id[MAXN],visit[MAXN],in[MAXN];
//root为根节点,n为节点数量,m为边数量
int zhuliu(int root,int n,int m,Edge edge[]){
//最小树形图的总权值
int res=0,u,v;
while(1){
//找每个节点的最小入边
//初始化所有入边边权无穷大
for(int i=0;i<n;i++)
in[i]=INF;
//对于每个边
for(int i=0;i<m;i++)
//如果该边不是自环边,该边的终点v顶点的入边边权比这条边大,那么这条边作为v顶点的入边
if(edge[i].u!=edge[i].v && edge[i].cost<in[edge[i].v]){
//入边的起始顶点
pre[edge[i].v]=edge[i].u;
//入边的边权
in[edge[i].v]=edge[i].cost;
}
//如果有除根节点以外的点的入边边权无穷大,那么不存在最小树形图
for(int i=0;i<n;i++)
if(i!=root && in[i]==INF)
return -1;
//找环
//环的数量
int tn=0;
memset(id,-1,sizeof(id));
memset(visit,-1,sizeof(visit));
in[root]=0;
//标记每个环
for(int i=0;i<n;i++){
//记录权值
res+=in[i];
v=i;
//寻找v节点所在的环
//visit保证不会无限循环,并且用i标记了该环是那个所有的顶点
while(visit[v]!=i && id[v]==-1 && v!=root){
visit[v]=i;
v=pre[v];
}
//标记环上的顶点是属于第tn个环
if(v!=root && id[v]==-1){
for(int u=pre[v];u!=v;u=pre[u])
id[u]=tn;
id[v]=tn++;
}
}
//无环,当前生成树就是最小树形图
if(tn==0) break;
//有环建立新图
for(int i=0;i<n;i++)
if(id[i]==-1)
id[i]=tn++;
for(int i=0;i<m;){
v=edge[i].v;
//用环号代替起始点,边是建立在两个还之间
edge[i].u=id[edge[i].u];
edge[i].v=id[edge[i].v];
//i边的权值要减去v所在环的入边权值
if(edge[i].u!=edge[i].v)
edge[i++].cost-=in[v];
//i边在连接的是同一个环里面的连个节点,该边舍去
else
swap(edge[i],edge[--m]);
}
//对新图求最小树形图
//新图的节点数量
n=tn;
//新图的根节点位置,缩点所在的位置
root=id[root];
}
//最小树形图的权值
return res;
}
int g[MAXN][MAXN];
int main(){
int n,m;
int t;
scanf("%d",&t);
for(int casei=1;casei<=t;casei++){
scanf("%d%d",&n,&m);
//初始化
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
g[i][j]=INF;
int u,v,cost;
while(m--){
scanf("%d%d%d",&u,&v,&cost);
if(u==v)continue;
g[u][v]=min(g[u][v],cost);
}
int L=0;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(g[i][j]<INF){
edge[L].u=i;
edge[L].v=j;
edge[L++].cost=g[i][j];
}
//参数分别为根节点,总结点数边数,边集合
int ans=zhuliu(0,n,L,edge);
printf("Case #%d: ",casei);
if(ans==-1)
printf("Possums!\n");
else
printf("%d\n",ans);
}
}
2.5、最小点基,最小权点基
针对有向图的一个支配集,当然也不算支配集,
从这个点集合出发,可以到达有向图的任意一个点。称作为一个点基本
我们只要从所有的最高强连通分量中各选一个点组成集合就能组成 **最小点基 **
取权值最小的就是最小权点基
缩点后重新建边就可以了
3、最短路
-
dij mlogn
-
spfa mk
-
floyd n^3
Floyd求最小环
Floyd基于松弛的动态规划
针对每一个k去松弛i-j的路径。
for(k,1,n)for(i,1,n)for(j,1,n)if(可松弛)松弛
最小环为负就是有负环
const int MAXN = 110;
const int INF = 0xffffff0;
int temp,Map[MAXN][MAXN],Dist[MAXN][MAXN],pre[MAXN][MAXN],ans[MAXN*3];
void Solve(int i,int j,int k)
{
temp = 0; //回溯,存储最小环
while(i != j)
{
ans[temp++] = j;
j = pre[i][j];
}
ans[temp++] = i;
ans[temp++] = k;
}
void Floyd(int N)
{
for(int i = 1; i <= N; ++i)
for(int j = 1; j <= N; ++j)
{
Dist[i][j] = Map[i][j];
pre[i][j] = i;
}
int MinCircle = INF; //最小环
for(int k = 1; k <= N; ++k)
{
for(int i = 1; i <= N; ++i)
{
for(int j = 1; j <= N; ++j)
{
if(i != j && Dist[i][j] != INF && Map[i][k] != INF && Map[k][j] != INF
&& Dist[i][j] + Map[i][k] + Map[k][j] < MinCircle)
{
MinCircle = min(MinCircle, Dist[i][j] + Map[i][k] + Map[k][j]);
Solve(i,j,k); //回溯存储最小环
}
}
}
for(int i = 1; i <= N; ++i)
{
for(int j = 1; j <= N; ++j)
{
if(Dist[i][k] != INF && Dist[k][j] != INF &&
Dist[i][k] + Dist[k][j] < Dist[i][j])
{
Dist[i][j] = Dist[i][k] + Dist[k][j];
pre[i][j] = pre[k][j]; //记录点i到点j的路径上,j前边的点
}
}
}
}
if(MinCircle == INF) //不存在环
{
printf("No solution.\n");
return;
}
//如果求出最小环为负的,原图必定存在负环
for(int i = 0;i < temp; ++i) //输出最小环
if(i != temp-1)
printf("%d ",ans[i]);
else
printf("%d\n",ans[i]);
}
4、k短路
使用spfa+A*可以处理负边权
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int maxn=100010;
int n,m,dis[maxn];
int tot,head1[maxn],head2[maxn];
bool flag[maxn];
struct edge{
int to,next,w;
}e[maxn*2],e2[maxn*2];
struct node{
int from,f,g;
bool operator < (node rhs)const{
return rhs.f==f?g>rhs.g:f>rhs.f;
}
};
void init(){
memset(head1,-1,sizeof head1);
memset(head2,-1,sizeof head2);
tot=0;
memset(flag,false,sizeof flag);
}
void add_edge(int u,int v,int w)
{
e[tot].to=v;
e[tot].w=w;
e[tot].next=head1[u];
head1[u]=tot;
e2[tot].to=u;//建反图
e2[tot].w=w;
e2[tot].next=head2[v];
head2[v]=tot;
tot++;
}
void spfa(int t)//反图预处理dis
{
for(int i=1;i<=n;i++)dis[i]=maxn;
dis[t]=0;
queue<int> q;q.push(t);
flag[t]=1;
while(!q.empty())
{
int v=q.front();
q.pop();flag[v]=0;
for(int i=head2[v];~i;i=e2[i].next)
if(dis[e2[i].to]>dis[v]+e2[i].w)
{
dis[e2[i].to]=dis[v]+e2[i].w;
if(!flag[e2[i].to])
{
q.push(e2[i].to);
flag[e2[i].to]=1;
}
}
}
}
int a_star(int s,int t,int k)
{
if(s==t) return 0;
if(dis[s]==maxn) return -1;
priority_queue<node> q;
int cnt=0;
node tmp,to;
tmp.from=s;
tmp.g=0;
tmp.f=tmp.g+dis[tmp.from];
q.push(tmp);
while(!q.empty())
{
tmp=q.top();
q.pop();
if(tmp.from==t) cnt++;
if(cnt==k) return tmp.g;
for(int i=head1[tmp.from];i;i=e[i].next)
{
to.from=e[i].to;
to.g=tmp.g+e[i].w;
to.f=to.g+dis[to.from];
q.push(to);
}
}
return -1;
}
int main()
{
int x,y,z,s,t,k;
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>x>>y>>z;
add_edge(x,y,z);
}
cin>>s>>t>>k;//输入起点,终点,k短路
spfa(t);
int ans=a_star(s,t,k);
cout<<ans;
return 0;
}
5、支配集、覆盖集、独立集。
定义
支配集,就是支配所有的点。
点覆盖,就是覆盖所有的边。
独立集,就是集合内所有点互相独立。
算法
- 贪心
首先是深度优先遍历,得到遍历序列。代码如下:
int p[maxn];
bool select[maxn];
int newpos[maxn];
int now;
int n,m;
void DFS(int x)
{
newpos[now++]=x;
int k;
for(k=head[x];k!=-1;k=edge[k].next)
{
if(!select[edge[k].to])
{
select[edge[k].to]=true;
p[edge[k].to]=x;
DFS(edge[k].to);
}
}
}
对于最小支配集,贪心函数如下:
int greedy()
{
bool s[maxn];
bool set[maxn]={0};
int ans=0;
int i;
for(i=n-1;i>=0;i--)
{
int t=newpos[i];
if(!s[t])
{
if(!set[p[t]])
{
set[p[t]]=true;
ans++;
}
s[t]=true;
s[p[t]]=true;
s[p[p[t]]]=true;
}
}
return ans;
}
对于最小点覆盖,贪心函数如下:
int greedy()
{
bool s[maxn]={0};
bool set[maxn]={0};
int ans=0;
int i;
for(i=n-1;i>=1;i--)
{
int t=newpos[i];
if(!s[t]&&s[p[t]])
{
set[p[t]]=true;
ans++;
s[t]=true;
s[p[t]]=true;
}
}
return ans;
}
对于最大独立集,贪心函数如下:
int greedy()
{
bool s[maxn]={0};
bool set[maxn]={0};
int ans=0;
int i;
for(i=n-1;i>=0;i--)
{
int t=newpos[i];
if(!s[t])
{
set[t]=true;
ans++;
s[t]=true;
s[p[t]]=true;
}
}
return ans;
}
- dp
最小支配集:
void DP(int u,int p)
{
dp[u][2]=0;
dp[u][0]=1;
bool s=false;
int sum=0,inc=INF;
int k;
for(k=head[u];k!=-1;k=edge[k].next)
{
int to=edge[k].to;
if(to==p)continue;
DP(to,u);
dp[u][0]+=min(dp[to][0],min(dp[u][1],dp[u][2]));
if(dp[to][0]<=dp[to][1])
{
sum+=dp[to][0];
s=true;
}
else
{
sum+=dp[to][1];
inc=min(inc,dp[to][0]-dp[to][1]);
}
if(dp[to][1]!=INF&&dp[u][2]!=INF)dp[u][2]+=dp[to][1];
else dp[u][2]=INF;
}
if(inc==INF&&!s)dp[u][1]=INF;
else
{
dp[u][1]=sum;
if(!s)dp[u][1]+=inc;
}
}
最小点覆盖:
void DP(int u,int p)
{
dp[u][0]=1;
dp[u][1]=0;
int k,to;
for(k=head[u];k!=-1;k=edge[k].next)
{
to=edge[k].to;
if(to==p)continue;
DP(to,u);
dp[u][0]+=min(dp[to][0],dp[to][1]);
dp[u][1]+=dp[to][0];
}
}
最大独立集:
void DP(int u,int p)
{
dp[u][0]=1;
dp[u][1]=0;
int k,to;
for(k=head[u];k!=-1;k=edge[k].next)
{
to=edge[k].to;
if(to==p)continue;
DP(to,u);
dp[u][0]+=dp[u][1];
dp[u][1]+=max(dp[to][0],dp[to][1]);
}
}
6、无向图的割点、桥和双连通分支的基本概念
参考kuangbin模板
其他概念模板
- 2-SAT
- 旅行背包问题
匈牙利算法
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define MAXL 100000
#define MAX 500
#define INF 1000000000
inline int read()
{
int x=0,t=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
int n1,n2;
struct Line
{
int v,next,w;
}e[MAXL];
int h[MAX],cnt;
inline void Add(int u,int v,int w)
{
e[cnt]=(Line){v,h[u],w};h[u]=cnt++;
e[cnt]=(Line){u,h[v],0};h[v]=cnt++;
}
int n,S,T;
int level[MAX];
bool BFS()
{
memset(level,0,sizeof(level));
queue<int> Q;
Q.push(S);level[S]=1;
while(!Q.empty())
{
int u=Q.front();Q.pop();
for(int i=h[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(e[i].w&&!level[v])
level[v]=level[u]+1,Q.push(v);
}
}
return level[T];
}
int cur[MAX];
int DFS(int u,int flow)
{
if(u==T||!flow)return flow;
int ret=0;
for(int &i=cur[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(e[i].w&&level[v]==level[u]+1)
{
int d=DFS(v,min(flow,e[i].w));
ret+=d;flow-=d;
e[i].w-=d;e[i^1].w+=d;
}
}
if(!ret)level[u]=0;
return ret;
}
int Dinic()
{
int ret=0;
while(BFS())
{
for(int i=S;i<=T;++i)cur[i]=h[i];
ret+=DFS(S,INF);
}
return ret;
}
int main()
{
freopen("flyer.in","r",stdin);
freopen("flyer.out","w",stdout);
n=read();n1=read();n2=n-n1;
S=0;T=n+1;
memset(h,-1,sizeof(h));
for(int i=1;i<=n1;++i)Add(S,i,1);
for(int i=n1+1;i<=n;++i)Add(i,T,1);
int u,v;
while(scanf("%d%d",&u,&v)!=EOF)
Add(u,v,1);
printf("%d\n",Dinic());
return 0;
}
网络流
1、最大流
- sap
int head[MAXN];
int gap[MAXN],dep[MAXN],pre[MAXN],cur[MAXN];
void init()
{
tol = 0;
memset(head,-1,sizeof(head));
}
//加边,单向图三个参数,双向图四个参数
void addedge(int u,int v,int w,int rw=0)
{
edge[tol].to =v;edge[tol].cap = w;edge[tol].next = head[u];
edge[tol].flow= 0;head[u] = tol++;
edge[tol].to =u;edge[tol].cap = rw;edge[tol].next = head[v];
edge[tol].flow= 0;head[v]=tol++;
}
//输入参数:起点、终点、点的总数
//点的编号没有影响,只要输入点的总数
int sap(int start,int end,int N)
{
memset(gap,0,sizeof(gap));
memset(dep,0,sizeof(dep));
memcpy(cur,head,sizeof(head));
int u = start;
pre[u] = -1;
gap[0] = N;
int ans = 0;
while(dep[start] < N)
{
if(u == end)
{
int Min = INF;
for(int i = pre[u];i != -1; i = pre[edge[i^1].to])
if(Min > edge[i].cap - edge[i].flow)
Min = edge[i].cap - edge[i].flow;
for(int i = pre[u];i != -1; i = pre[edge[i^1].to])
{
edge[i].flow += Min;
edge[i^1].flow -= Min;
}
u = start;
ans += Min;
continue;
}
bool flag = false;
int v;
for(int i = cur[u]; i != -1;i = edge[i].next)
{
v = edge[i].to;
if(edge[i].cap - edge[i].flow && dep[v]+1 == dep[u])
{
flag = true;
cur[u] = pre[v] = i;
break;
}
}
if(flag)
{
u = v;
continue;
}
int Min = N;
for(int i = head[u]; i != -1;i = edge[i].next)
if(edge[i].cap - edge[i].flow && dep[edge[i].to] < Min)
{
Min = dep[edge[i].to];
cur[u] = i;
}
gap[dep[u]]--;
if(!gap[dep[u]])return ans;
dep[u] = Min+1;
gap[dep[u]]++;
if(u != start) u = edge[pre[u]^1].to;
}
return ans;
}
-
上下界网络流
#include <algorithm> #include <cstring> #include <cstdio> #include <queue> using namespace std; const int INF = 0x3f3f3f3f; const int MAXN = 110; namespace ISAP { const int MAXV = MAXN; const int MAXE = ( MAXV*MAXV/2 + MAXV*2 )*3; struct Edge { int u, v, c, f; Edge(){} Edge( int u, int v, int c, int f ): u(u),v(v),c(c),f(f){} }edge[MAXE<<1]; int n, m, s, t, ss, tt; int head[MAXV], nxt[MAXE<<1], eid[MAXE<<1], eidx; void init( int n2, int ss2, int tt2 ) { // 初始化,设置附加源和附加汇 n = n2; ss = ss2; tt = tt2; m = eidx = 0; memset( head, -1, sizeof(head) ); } int adde( int u, int v, int c ) { // 添加一条只有上界的边 int rtn = m; eid[eidx] = m; nxt[eidx] = head[u]; head[u] = eidx++; edge[m++] = Edge(u,v,c,0); eid[eidx] = m; nxt[eidx] = head[v]; head[v] = eidx++; edge[m++] = Edge(v,u,0,0); return rtn; } int adde2( int u, int v, int b, int c ) { // 添加一条有上下界的边,返回边的下标 int rtn = adde(u,v,c-b); adde(ss,v,b); adde(u,tt,b); return rtn; } // 以下ISAP板子 int prev[MAXV], dist[MAXV], num[MAXV], cur[MAXV], res[MAXV]; queue<int> bfsq; void bfs() { for( int i = 1; i <= n; ++i ) dist[i] = n; dist[t] = 0; bfsq.push(t); while( !bfsq.empty() ) { int u = bfsq.front(); bfsq.pop(); for( int i = head[u]; ~i; i = nxt[i] ) { Edge &e = edge[eid[i]]; if( dist[e.v] == n ) { dist[e.v] = dist[u] + 1; bfsq.push(e.v); } } } } void augment() { int u = t, flow = res[t]; while( u != s ) { int i = prev[u]; edge[i].f += flow; edge[i^1].f -= flow; u = edge[i].u; } } bool advance( int &u ) { for( int i = cur[u]; ~i; i = nxt[i] ) { Edge &e = edge[eid[i]]; if( e.c > e.f && dist[e.v] + 1 == dist[u] ) { prev[e.v] = cur[u] = i; res[e.v] = min( res[u], e.c - e.f ); u = e.v; return true; } } return false; } bool retreat( int &u ) { if( --num[dist[u]] == 0 ) return false; int newd = n; for( int i = head[u]; ~i; i = nxt[i] ) { Edge &e = edge[eid[i]]; if( e.c > e.f ) newd = min( newd, dist[e.v] + 1 ); } ++num[ dist[u] = newd ]; cur[u] = head[u]; if( u != s ) u = edge[prev[u]].u; return true; } int solve( int s2, int t2 ) { // 以s2为源,t2为汇跑最大流 s = s2; t = t2; bfs(); for( int i = 1; i <= n; ++i ) cur[i] = head[i], ++num[dist[i]]; int u = s, flow = 0; res[s] = INF; while( dist[s] < n ) { if( u == t ) { augment(); flow += res[t]; u = s; } if( !advance(u) ) if( !retreat(u) ) break; } return flow; } } int n, s, t, ss, tt; // 点的个数,源,汇,附加源,附加汇 namespace Solve { using ISAP::head; using ISAP::nxt; using ISAP::eid; using ISAP::Edge; using ISAP::edge; bool first; void dfs( int u ) { // dfs输出方案 // printf( "Debug: u = %d\n", u ); if( !first ) putchar(' '); first = false; printf( "%d", u ); for( int i = head[u]; ~i; i = nxt[i] ) { Edge &e = edge[eid[i]]; if( e.v <= n && e.f > 0 ) { // 任选一条边走下去 // printf( "going eid = %d, from %d to %d, flow_left = %d\n", eid[i], e.u, e.v, e.f ); --e.f; dfs(e.v); return; } } } void addbound() { // 把每条边流量加上下界,恢复成原图的样子,方便输出方案 using ISAP::m; for( int i = 0; i < m; ++i ) { Edge &e = edge[eid[i]]; if( e.u <= n && e.v <= n && e.c > 0 ) ++e.f; } } } namespace Debug { // 调试用QwQ void print_flow() { using ISAP::edge; using ISAP::Edge; using ISAP::eid; using ISAP::m; for( int i = 0; i < m; ++i ) { Edge &e = edge[eid[i]]; if( e.u <= n && e.v <= n && e.c > 0 ) printf( "eid = %d, from %d to %d, flow = %d\n", eid[i], e.u, e.v, e.f ); } } void print_flow2() { using ISAP::edge; using ISAP::Edge; using ISAP::eid; using ISAP::m; for( int i = 0; i < m; ++i ) { Edge &e = edge[eid[i]]; if( e.f > 0 ) printf( "eid = %d, from %d to %d, flow = %d\n", eid[i], e.u, e.v, e.f ); } } } int main() { while( scanf( "%d", &n ) == 1 ) { s = n+1, t = n+2, ss = n+3, tt = n+4; ISAP::init(tt,ss,tt); for( int i = 1; i <= n; ++i ) { int mi; scanf( "%d", &mi ); while( mi-- ) { int v; scanf( "%d", &v ); ISAP::adde2(i,v,1,INF); } ISAP::adde2(s,i,0,INF); ISAP::adde2(i,t,0,INF); } int flow1 = ISAP::solve(ss,tt); // printf( "flow1 = %d\n", flow1 ); // Debug::print_flow(); // Debug::print_flow2(); int tsedge = ISAP::adde2(t,s,0,INF); // 存储弧<t,s>的信息,调试用QwQ int ans = ISAP::solve(ss,tt); // printf( "t_s flow = %d\n", ISAP::edge[tsedge].f ); // Debug::print_flow(); // Debug::print_flow2(); printf( "%d\n", ans ); Solve::addbound(); // 把每条图中的边流量加上下界,恢复成原图的样子,方便输出方案 while( ans ) { using namespace Solve; for( int i = head[s]; ~i; i = nxt[i] ) { Edge &e = edge[eid[i]]; if( e.v <= n && e.f > 0 ) { // 任选一个点dfs,输出方案 first = true; --e.f; --ans; dfs(e.v); putchar('\n'); } } } } return 0; }
2、最小费用最大流
原理就是一直增广 使用spfa(因为有负边权)找到的s->t的一条可行最短路,直到不存在最短路了。可以证明这样形成的最大流费用是最小的。
最大费用修改spfa或者直接建负边权即可。
#include<bits/stdc++.h>
/*
支持重边,所以边数自己定,最大费用改成负权即可。
*/
using namespace std;
const int MAXN=1e4+10;
const int MAXM=2e5+10;
const int INF=0x3f3f3f3f;
struct Edge{
int to,next,cap,flow,cost;
}edge[MAXM];
int tol,N;
int head[MAXN],pre[MAXN],dis[MAXN];
bool vis[MAXN];
void init(int n){
N=n;
tol=0;
memset(head,-1,sizeof head);
}
void addedge(int u,int v,int cap,int cost){
Edge &e=edge[tol];
e.to=v;
e.cap=cap;
e.cost=cost;
e.flow=0;
e.next=head[u];
head[u]=tol++;
Edge &ee=edge[tol];
ee.to=u;
ee.cap=0;
ee.cost=-cost;
ee.flow=0;
ee.next=head[v];
head[v]=tol++;
}
bool spfa(int s,int t){
//初始化
queue<int> q;
for(int i=0;i<=N;i++)
dis[i]=INF,vis[i]=false,pre[i]=-1;
dis[s]=0;vis[s]=true;q.push(s);
//松弛,没有判断负环,判负环用cnt[N];
while(!q.empty()){
int u=q.front();q.pop();vis[u]=false;
for(int i=head[u];~i;i=edge[i].next){
Edge &e=edge[i];int v=e.to;
if(e.cap>e.flow && dis[v]>dis[u]+e.cost){
dis[v]=dis[u]+e.cost;
pre[v]=i;
if(!vis[v]){
vis[v]=true;
q.push(v);
}
}
}
}
if(pre[t]==-1)return false;
else return true;
}
void mcmf(int s,int t,int &cost,int &flow){
flow=0;cost=0;
//找一条费用最小的可行流。增广之。
while(spfa(s,t)){
int Min=INF;
for(int i=pre[t];~i;i=pre[edge[i^1].to])if(Min>edge[i].cap-edge[i].flow){
Min=edge[i].cap-edge[i].flow;
}
//这里i 是边(u,v)的下标,让edge[i^1].to=u
for(int i=pre[t];~i;i=pre[edge[i^1].to]){
//debug
//cout<<"pre="<<edge[i^1].to<<endl;
edge[i].flow+=Min;
edge[i^1].flow-=Min;
cost+=edge[i].cost*Min;
}
flow+=Min;
}
}