圖論相關知識
阿新 • • 發佈:2018-11-10
簡單介紹
就我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;
}
}