二分圖最大匹配問題。
【建模方法】
在二分圖的基礎上增加源S和匯T
1、S向X集合中每個頂點連一條容量爲1的有向邊。
2、Y集合中每個頂點向T連一條容量爲1的有向邊。
3、XY集合之間的邊都設爲從A集合中的點到B集合之中的點,容量爲1的有向邊。
求網絡最大流,流量就是匹配數,所有滿流邊是一組可行解。
【建模分析】
基本的二分圖最大匹配,可以直接用匈牙利算法或Hopcroft_Karp算法解決,更一般的方法是網絡最大流。
例題:
【問題描述】
飛行大隊有若干個來自各地的駕駛員,專門駕駛一種型號的飛機,這種飛機每架有兩個駕駛員,需一個正駕駛員和一個副駕駛員。由於種種原因,例如相互配合的問題,有些駕駛員不能在同一架飛機上飛行,問如何搭配駕駛員才能使出航的飛機最多。
如圖,假設有10個駕駛員,如圖中的V1,V2,…,V10就代表達10個駕駛員,其中V1,V2,V3,V4,V5是正駕駛員,V6,V7,V8,V9,V10是副駕駛員。如果一個正駕駛員和一個副駕駛員可以同機飛行,就在代表他們兩個之間連一條線,兩個人不能同機飛行,就不連。例如V1和V7可以同機飛行,而V1和V8就不行。請搭配飛行員,使出航的飛機最多。注意:因爲駕駛工作分工嚴格,兩個正駕駛員或兩個副駕駛員都不能同機飛行.
【輸入格式】
輸入文件有若干行
第一行,兩個整數n與n1,表示共有n個飛行員(2<=n<=100),其中有n1名飛行員是正駕駛員.
下面有若干行,每行有2個數字a,b。表示正駕駛員a和副駕駛員b可以同機飛行。
注:正駕駛員的編號在前,即正駕駛員的編號小於副駕駛員的編號.
【輸出格式】
輸出文件有一行
第一行,1個整數,表示最大起飛的飛機數。
【輸入輸出樣例】
輸入文件名: flyer.in
10 5
1 7
2 6
2 10
3 7
4 8
5 9
輸出文件名:flyer.out
4
【問題分析】
二分圖最大匹配問題。
【建模方法】
在二分圖的基礎上增加源S和匯T。
1、S向X集合中每個頂點連一條容量爲1的有向邊。
2、Y集合中每個頂點向T連一條容量爲1的有向邊。
3、XY集合之間的邊都設爲從A集合中的點到B集合之中的點,容量爲1的有向邊。
求網絡最大流,流量就是匹配數,所有滿流邊是一組可行解。
【建模分析】
基本的二分圖最大匹配,可以直接用匈牙利算法或Hopcroft_Karp算法解決,更一般的方法是網絡最大流。
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<stack>
#include<set>
#include<iomanip>
//#define mem(dp,a) memset(dp,a,sizeof(dp))
//#define fo(i,n) for(int i=0;i<(n);i++)
//#define INF 0x3f3f3f3f
#define fread() freopen("data.txt","r",stdin)
#define fwrite() freopen("out.out","w",stdout)
using namespace std;
typedef long long ll;
//最小費用最大流,求最大費用只需要取相反數,結果取相反數即可。
//點的總數爲 N,點的編號 0~N-1
const int MAXN = 505;
const int MAXM = 100005;//要比題目給的大
const int INF = 0x3f3f3f3f;
struct Edge
{
int to, next, cap, flow, cost;
int x, y;
} edge[MAXM],HH[MAXN],MM[MAXN];
int head[MAXN],tol;
int pre[MAXN],dis[MAXN];
bool vis[MAXN];
int N, M;
void init(int n)
{
N = n;
tol = 0;
memset(head, -1, sizeof(head));
}
void addedge(int u, int v, int cap, int cost)//左端點,右端點,容量,花費
{
edge[tol]. to = v;
edge[tol]. cap = cap;
edge[tol]. cost = cost;
edge[tol]. flow = 0;
edge[tol]. next = head[u];
head[u] = tol++;
edge[tol]. to = u;
edge[tol]. cap = 0;
edge[tol]. cost = -cost;
edge[tol]. flow = 0;
edge[tol]. 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);
while(!q.empty())
{
int u = q.front();
q.pop();
vis[u] = false;
for(int i = head[u]; i != -1; i = edge[i]. next)
{
int v = edge[i]. to;
if(edge[i]. cap > edge[i]. flow &&
dis[v] > dis[u] + edge[i]. cost )
{
dis[v] = dis[u] + edge[i]. cost;
pre[v] = i;
if(!vis[v])
{
vis[v] = true;
q.push(v);
}
}
}
}
if(pre[t] == -1) return false;
else return true;
}
/*
* 直接調用獲取最小費用和最大流
* 輸入: start-源點,end-匯點(編號從0開始)
* 返回值: pair<int,int> 第一個是最小費用,第二個是最大流
*/
pair<int, int> minCostMaxflow(int s, int t)
{
int flow = 0;
int cost = 0;
while(spfa(s,t))
{
int Min = INF;
for(int i = pre[t]; i != -1; i = pre[edge[i^1]. to])
{
if(Min > edge[i]. cap - edge[i]. flow)
Min = edge[i]. cap - edge[i]. flow;
}
// int percost=0;
for(int i = pre[t]; i != -1; i = pre[edge[i^1]. to])
{
edge[i]. flow += Min;
edge[i^1]. flow -= Min;
cost += edge[i]. cost * Min;
// percost+=edge[i].cost;
}
// if(percost>0){
// return make_pair(cost, flow);
// }
// cost+=percost*Min;
flow += Min;
}
return make_pair(cost, flow);
}
int a,b,c,d;
int u,v,k;
int m,n;
int main()
{
ios_base::sync_with_stdio(false);
// fread();
freopen("flyer.in","r",stdin);
freopen("flyer.out","w",stdout);
while(cin >> n>>m)
{
init(n+2);
while(cin >>u>>v)
{
addedge(u,v,1,0);
// addedge(v,u,1,0);
}
for(int i=1;i<=m;i++)
{
addedge(0,i,1,0);
}
for(int i=m+1;i<=n;i++)
{
addedge(i,n+1,1,0);
}
cout << minCostMaxflow(0,n+1).second <<endl;
}
return 0;
}
最大權閉合圖
【問題分析】
最大權閉合圖問題,可以轉化成最小割問題,進而用最大流解決。
【建模方法】
把每個實驗看作二分圖X集合中的頂點,每個設備看作二分圖Y集合中的頂點,增加源S和匯T。
1、從S向每個Xi連接一條容量爲該點收入的有向邊。
2、從Yi向T連接一條容量爲該點支出的有向邊。
3、如果一個實驗i需要設備j,連接一條從Xi到Yj容量爲無窮大的有向邊。
統計出所有實驗的收入只和Total,求網絡最大流Maxflow,最大收益就是Total-Maxflow。對應的解就是最小割劃分出的S集合中的點,也就是最後一次增廣找到阻塞流時能從S訪問到的頂點。
【建模分析】
定義一個割劃分出的S集合爲一個解,那麼割集的容量之和就是(未被選的A集合中的頂點的權值 + 被選的B集合中的頂點的權值),記爲Cut。A集合中所有頂點的權值之和記爲Total,那麼Total - Cut就是(被選的A集合中的頂點的權值 - 被選的B集合中的頂點的權值),即爲我們的目標函數,記爲A。要想最大化目標函數A,就要儘可能使Cut小,Total是固定值,所以目標函數A取得最大值的時候,Cut最小,即爲最小割。
該問題的一般模型爲最大權閉合圖,相關討論見《最小割模型在信息學競賽中的應用》作者胡伯濤。
例題:
【問題描述】
W 教授正在爲國家航天中心計劃一系列的太空飛行。每次太空飛行可進行一系列商業性實驗而獲取利潤。現已確定了一個可供選擇的實驗集合E={E1,E2,…,Em},和進行這些實驗需要使用的全部儀器的集合I={ I1, I2,…,In }。實驗Ej 需要用到的儀器是I的子集Rj∈I。配置儀器Ik 的費用爲ck 美元。實驗Ej 的贊助商已同意爲該實驗結果支付pj 美元。W教授的任務是找出一個有效算法,確定在一次太空飛行中要進行哪些實驗並因此而配置哪些儀器才能使太空飛行的淨收益最大。這裏淨收益是指進行實驗所獲得的全部收入與配置儀器的全部費用的差額。
【編程任務】
對於給定的實驗和儀器配置情況,編程找出淨收益最大的試驗計劃。
【數據輸入】
第1行有2個正整數m和n(m,n <= 100)。m是實驗數,n是儀器數。接下來的m行,每行是一個實驗的有關數據。第一個數贊助商同意支付該實驗的費用;接着是該實驗需要用到的若干儀器的編號。最後一行的n個數是配置每個儀器的費用。
【結果輸出】
第1行是實驗編號;第2行是儀器編號;最後一行是淨收益。
【輸入文件示例】shuttle.in
2 3
10 1 2
25 2 3
5 6 7
【輸出文件示例】shuttle.out
1 2
1 2 3
17
【問題分析】
最大權閉合圖問題,可以轉化成最小割問題,進而用最大流解決。
【建模方法】
把每個實驗看作二分圖X集合中的頂點,每個設備看作二分圖Y集合中的頂點,增加源S和匯T。
1、從S向每個Xi連接一條容量爲該點收入的有向邊。
2、從Yi向T連接一條容量爲該點支出的有向邊。
3、如果一個實驗i需要設備j,連接一條從Xi到Yj容量爲無窮大的有向邊。
統計出所有實驗的收入只和Total,求網絡最大流Maxflow,最大收益就是Total-Maxflow。對應的解就是最小割劃分出的S集合中的點,也就是最後一次增廣找到阻塞流時能從S訪問到的頂點。
【建模分析】
定義一個割劃分出的S集合爲一個解,那麼割集的容量之和就是(未被選的A集合中的頂點的權值 + 被選的B集合中的頂點的權值),記爲Cut。A集合中所有頂點的權值之和記爲Total,那麼Total - Cut就是(被選的A集合中的頂點的權值 - 被選的B集合中的頂點的權值),即爲我們的目標函數,記爲A。要想最大化目標函數A,就要儘可能使Cut小,Total是固定值,所以目標函數A取得最大值的時候,Cut最小,即爲最小割。
該問題的一般模型爲最大權閉合圖,相關討論見《最小割模型在信息學競賽中的應用》作者胡伯濤。
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<stack>
#include<set>
#include<iomanip>
//#define mem(dp,a) memset(dp,a,sizeof(dp))
//#define fo(i,n) for(int i=0;i<(n);i++)
//#define INF 0x3f3f3f3f
#define fread() freopen("data.txt","r",stdin)
#define fwrite() freopen("out.out","w",stdout)
using namespace std;
typedef long long ll;
vector<int> re;
int s,t;
int m,n,t1,t2;
int ans;
const int MAXN=100000,MAXM=100000,inf=1e9;
struct Edge
{
int v,c,f,nx;
Edge() {}
Edge(int v,int c,int f,int nx):v(v),c(c),f(f),nx(nx) {}
} E[MAXM];
int G[MAXN],cur[MAXN],pre[MAXN],dis[MAXN],gap[MAXN],N,sz;
void init(int _n) //初始化
{
N=_n,sz=0; memset(G,-1,sizeof(G[0])*N);
}
void link(int u,int v,int c)//連接兩個點
{
E[sz]=Edge(v,c,0,G[u]); G[u]=sz++;
E[sz]=Edge(u,0,0,G[v]); G[v]=sz++;
}
int ISAP(int S,int T)
{//S -> T
int maxflow=0,aug=inf,flag=false,u,v;
for (int i=0;i<N;++i)cur[i]=G[i],gap[i]=dis[i]=0;
for (gap[S]=N,u=pre[S]=S;dis[S]<N;flag=false)
{
for (int &it=cur[u];~it;it=E[it].nx)
{
if (E[it].c>E[it].f&&dis[u]==dis[v=E[it].v]+1)
{
if (aug>E[it].c-E[it].f) aug=E[it].c-E[it].f;
pre[v]=u,u=v; flag=true;
if (u==T)
{
for (maxflow+=aug;u!=S;)
{
E[cur[u=pre[u]]].f+=aug;
E[cur[u]^1].f-=aug;
}
aug=inf;
}
break;
}
}
if (flag) continue;
int mx=N;
for (int it=G[u];~it;it=E[it].nx)
{
if (E[it].c>E[it].f&&dis[E[it].v]<mx)
{
mx=dis[E[it].v]; cur[u]=it;
}
}
if ((--gap[dis[u]])==0) break;
++gap[dis[u]=mx+1]; u=pre[u];
}
return maxflow;
}
bool bfs(int S,int T)
{
static int Q[MAXN];
memset(dis,-1,sizeof(dis[0])*N);
dis[S]=0;
Q[0]=S;
for (int h=0,t=1,u,v,it ; h<t ; ++h)
{
for ( u=Q[h],it=G[u] ; ~it ; it=E[it].nx)
{
if (dis[v=E[it].v]==-1 && E[it].c > E[it].f)
{
dis[v]=dis[u]+1;
Q[t++]=v;
}
}
}
return dis[T]!=-1;
}
int dfs(int u,int T,int low)
{
if (u==T) return low;
int ret=0,tmp,v;
for (int &it=cur[u];~it&&ret<low;it=E[it].nx)
{
if (dis[v=E[it].v]==dis[u]+1&&E[it].c>E[it].f)
{
if (tmp=dfs(v,T,min(low-ret,E[it].c-E[it].f)))
{
ret+=tmp;
E[it].f+=tmp;
E[it^1].f-=tmp;
// low -= tmp;
// if(low==0)break;
}
}
}
if (!ret) dis[u]=-1;
return ret;
}
int dinic(int S,int T)
{
int maxflow=0,tmp;
while (bfs(S,T))
{
memcpy(cur,G,sizeof(G[0])*N);
while (tmp=dfs(S,T,inf)) maxflow+=tmp;
}
return maxflow;
}
int main()
{
ios_base::sync_with_stdio(false);
// fread();
freopen("shuttle.in","r",stdin);
freopen("shuttle.out","w",stdout);
scanf("%d%d",&m,&n);
s=0; t=n+m+1;
init(t+1);
for (int i=1;i<=m;i++)
{
scanf("%d",&t1);
link(s,i,t1);
ans += t1;
for (;;)
{
do t1=getchar(); while (t1==' '); ungetc(t1,stdin);
if (t1==10 || t1==13) break;
scanf("%d",&t2);
link(i,t2+m,inf);
}
}
for (int i=1;i<=n;i++)
{
scanf("%d",&t1);
link(i+m,t,t1);
}
int tmp_ans = dinic(s,t);;
// for(int i = 0;i <=m+n;i++)
// {
// cout << dis[i]<<" ";
// }
// cout <<endl;
for(int i = 1;i <= m;++i)
if(dis[i]!=-1)
cout<<i<<" ";
cout<<endl;
for(int i = m+1;i <= m+n;++i)
if(dis[i]!=-1)
cout << i-m<<" ";
cout <<endl;
cout << ans - tmp_ans <<endl;
return 0;
}
有向無環圖最小路徑覆蓋
【問題分析】
有向無環圖最小路徑覆蓋,可以轉化成二分圖最大匹配問題,從而用最大流解決。
【建模方法】
構造二分圖,把原圖每個頂點i拆分成二分圖X,Y集合中的兩個頂點Xi和Yi。對於原圖中存在的每條邊(i,j),在二分圖中連接邊(Xi,Yj)。然後把二分圖最大匹配模型轉化爲網絡流模型,求網絡最大流。
最小路徑覆蓋的條數,就是原圖頂點數,減去二分圖最大匹配數。沿着匹配邊查找,就是一個路徑上的點,輸出所有路徑即可。
【建模分析】
對於一個路徑覆蓋,有如下性質:
1、每個頂點屬於且只屬於一個路徑。
2、路徑上除終點外,從每個頂點出發只有一條邊指向路徑上的另一頂點。
所以我們可以把每個頂點理解成兩個頂點,一個是出發,一個是目標,建立二分圖模型。該二分圖的任何一個匹配方案,都對應了一個路徑覆蓋方案。如果匹配數爲0,那麼顯然路徑數=頂點數。每增加一條匹配邊,那麼路徑覆蓋數就減少一個,所以路徑數=頂點數 - 匹配數。要想使路徑數最少,則應最大化匹配數,所以要求二分圖的最大匹配。
注意,此建模方法求最小路徑覆蓋僅適用於有向無環圖,如果有環或是無向圖,那麼有可能求出的一些環覆蓋,而不是路徑覆蓋。
例題:
- [網絡流24題] 最小路徑覆蓋問題
★★☆ 輸入文件:path3.in 輸出文件:path3.out 評測插件
時間限制:1 s 內存限制:128 MB
算法實現題8-3 最小路徑覆蓋問題(習題8-13)
´問題描述:
給定有向圖G=(V,E)。設P是G的一個簡單路(頂點不相交)的集合。如果V中每個
頂點恰好在P的一條路上,則稱P是G的一個路徑覆蓋。P中路徑可以從V的任何一個頂
點開始,長度也是任意的,特別地,可以爲0。G的最小路徑覆蓋是G的所含路徑條數最少
的路徑覆蓋。
設計一個有效算法求一個有向無環圖G的最小路徑覆蓋。
提示:
設V={1,2,… ,n},構造網絡G1=(V1,E1)如下:
每條邊的容量均爲1。求網絡G1的(x0,y0)最大流。
´編程任務:
對於給定的給定有向無環圖G,編程找出G的一個最小路徑覆蓋。
´數據輸入:
由文件input.txt提供輸入數據。文件第1行有2個正整數n和m。n是給定有向無環圖
G的頂點數,m是G的邊數。接下來的m行,每行有2個正整數i 和j,表示一條有向邊(i,j)。
´結果輸出:
程序運行結束時,將最小路徑覆蓋輸出到文件output.txt中。從第1行開始,每行輸出
一條路徑。文件的最後一行是最少路徑數。
【問題分析】
有向無環圖最小路徑覆蓋,可以轉化成二分圖最大匹配問題,從而用最大流解決。
【建模方法】
構造二分圖,把原圖每個頂點i拆分成二分圖X,Y集合中的兩個頂點Xi和Yi。對於原圖中存在的每條邊(i,j),在二分圖中連接邊(Xi,Yj)。然後把二分圖最大匹配模型轉化爲網絡流模型,求網絡最大流。
最小路徑覆蓋的條數,就是原圖頂點數,減去二分圖最大匹配數。沿着匹配邊查找,就是一個路徑上的點,輸出所有路徑即可。
【建模分析】
對於一個路徑覆蓋,有如下性質:
1、每個頂點屬於且只屬於一個路徑。
2、路徑上除終點外,從每個頂點出發只有一條邊指向路徑上的另一頂點。
所以我們可以把每個頂點理解成兩個頂點,一個是出發,一個是目標,建立二分圖模型。該二分圖的任何一個匹配方案,都對應了一個路徑覆蓋方案。如果匹配數爲0,那麼顯然路徑數=頂點數。每增加一條匹配邊,那麼路徑覆蓋數就減少一個,所以路徑數=頂點數 - 匹配數。要想使路徑數最少,則應最大化匹配數,所以要求二分圖的最大匹配。
注意,此建模方法求最小路徑覆蓋僅適用於有向無環圖,如果有環或是無向圖,那麼有可能求出的一些環覆蓋,而不是路徑覆蓋。
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<stack>
#include<set>
#include<iomanip>
//#define mem(dp,a) memset(dp,a,sizeof(dp))
//#define fo(i,n) for(int i=0;i<(n);i++)
//#define INF 0x3f3f3f3f
#define fread() freopen("data.txt","r",stdin)
#define fwrite() freopen("out.out","w",stdout)
using namespace std;
typedef long long ll;
vector<int> re;
int s,t;
int m,n,t1,t2;
int ans;
const int MAXN=100000,MAXM=100000,inf=1e9;
struct Edge
{
int v,c,f,nx;
Edge() {}
Edge(int v,int c,int f,int nx):v(v),c(c),f(f),nx(nx) {}
} E[MAXM];
int G[MAXN],cur[MAXN],pre[MAXN],dis[MAXN],gap[MAXN],N,sz;
int tonex[MAXN];//記錄下一個節點
bool vis[MAXN];//
//dis[]層次
void init(int _n) //初始化
{
N=_n,sz=0; memset(G,-1,sizeof(G[0])*N);
}
void link(int u,int v,int c)//連接兩個點
{
E[sz]=Edge(v,c,0,G[u]); G[u]=sz++;
E[sz]=Edge(u,0,0,G[v]); G[v]=sz++;
}
int ISAP(int S,int T)
{//S -> T
int maxflow=0,aug=inf,flag=false,u,v;
for (int i=0;i<N;++i)cur[i]=G[i],gap[i]=dis[i]=0;
for (gap[S]=N,u=pre[S]=S;dis[S]<N;flag=false)
{
for (int &it=cur[u];~it;it=E[it].nx)
{
if (E[it].c>E[it].f&&dis[u]==dis[v=E[it].v]+1)
{
if (aug>E[it].c-E[it].f) aug=E[it].c-E[it].f;
pre[v]=u,u=v; flag=true;
if (u==T)
{
for (maxflow+=aug;u!=S;)
{
E[cur[u=pre[u]]].f+=aug;
E[cur[u]^1].f-=aug;
}
aug=inf;
}
break;
}
}
if (flag) continue;
int mx=N;
for (int it=G[u];~it;it=E[it].nx)
{
if (E[it].c>E[it].f&&dis[E[it].v]<mx)
{
mx=dis[E[it].v]; cur[u]=it;
}
}
if ((--gap[dis[u]])==0) break;
++gap[dis[u]=mx+1]; u=pre[u];
}
return maxflow;
}
bool bfs(int S,int T)
{
static int Q[MAXN];
memset(dis,-1,sizeof(dis[0])*N);
dis[S]=0;
Q[0]=S;
//dis[i]爲-1表示沒有
for (int h=0,t=1,u,v,it ; h<t ; ++h)
{
for ( u=Q[h],it=G[u] ; ~it ; it=E[it].nx)
{
if (dis[v=E[it].v]==-1 && E[it].c > E[it].f)
{
dis[v]=dis[u]+1;
Q[t++]=v;
}
}
}
return dis[T]!=-1;
}
int dfs(int u,int T,int low)
{
if (u==T) return low;
int ret=0,tmp,v;
for (int &it=cur[u];~it&&ret<low;it=E[it].nx)
{
if (dis[v=E[it].v]==dis[u]+1&&E[it].c>E[it].f)
{
if (tmp=dfs(v,T,min(low-ret,E[it].c-E[it].f)))
{
if(v-n>0) vis[v-n]=1;
tonex[u] = v;
ret+=tmp;
E[it].f+=tmp;
E[it^1].f-=tmp;
// low -= tmp;
// if(low==0)break;
}
}
}
if (!ret) dis[u]=-1;
return ret;
}
int dinic(int S,int T)
{
int maxflow=0,tmp;
while (bfs(S,T))
{
memcpy(cur,G,sizeof(G[0])*N);
while (tmp=dfs(S,T,inf)) maxflow+=tmp;
}
return maxflow;
}
int main()
{
ios_base::sync_with_stdio(false);
// fread();
freopen("path3.in","r",stdin);
freopen("path3.out","w",stdout);
cin >> n>> m;
s=0; t=n+n+1;
init(t+1);
for(int i=1;i<=n;i++)
{
link(s,i,1);
}
for(int i=n+1;i<=2*n;i++)
{
link(i,t,1);
}
for(int i = 0 ;i < m;i++)
{
cin >> t1 >> t2;
link(t1,t2+n,inf);
}
int tmp_ans = dinic(s,t);
for(int i=1;i<=n;i++)
{
if(vis[i]) continue;
cout<<i;
int k=i;
while(tonex[k])
{
cout<<" "<<tonex[k]-n;
k=tonex[k]-n;
}
cout<<endl;
}
cout << n - tmp_ans <<endl;
return 0;
}
二分圖多重匹配
【問題分析】
二分圖多重匹配問題,可以用最大流解決。
【建模方法】
建立二分圖,每個單位爲X集合中的頂點,每個餐桌爲Y集合中的頂點,增設附加源S和匯T。
1、從S向每個Xi頂點連接一條容量爲該單位人數的有向邊。
2、從每個Yi頂點向T連接一條容量爲該餐桌容量的有向邊。
3、X集合中每個頂點向Y集合中每個頂點連接一條容量爲1的有向邊。
求網絡最大流,如果最大流量等於所有單位人數之和,則存在解,否則無解。對於每個單位,從X集合對應點出發的所有滿流邊指向的Y集合的頂點就是該單位人員的安排情況(一個可行解)。
【建模分析】
對於一個二分圖,每個頂點可以有多個匹配頂點,稱這類問題爲二分圖多重匹配問題。X,Y集合之間的邊容量全部是1,保證兩個點只能匹配一次(一個餐桌上只能有一個單位的一個人),源匯的連邊限制了每個點匹配的個數。求出網絡最大流,如果流量等於X集合所有點與S邊容量之和,那麼則說明X集合每個點都有完備的多重匹配。
【問題另解】
貪心,更好的方法其實是貪心。首先把所有單位和餐桌按人數從大到小排序,一種適當的貪心策略就是對於每個單位,所有人每次儘量去剩餘容量較大的餐桌就坐。按照這種貪心策略,如果某時發現有人已經無法就坐,則無解。具體方法爲用線段樹維護餐桌的剩餘容量,按人數從多到少安排每個單位的人員,每次安排就是把容量餐桌前k大的餐桌人數減1(k爲該單位人數)。爲保證線段樹前k位時刻爲前k大,要維護第k與第k+1,k+2,…人數與第k相等的位置,減少第k大時要減少儘量靠後的,這樣才能保證單調。
例題:
[網絡流24題] 圓桌聚餐
★★☆ 輸入文件:roundtable.in 輸出文件:roundtable.out 評測插件
時間限制:1 s 內存限制:128 MB
«問題描述:
假設有來自m 個不同單位的代表參加一次國際會議。每個單位的代表數分別爲
ri(i=1,2,3…m), 。會議餐廳共有n張餐桌,每張餐桌可容納c i(i=1,2…n) 個代表就餐。
爲了使代表們充分交流,希望從同一個單位來的代表不在同一個餐桌就餐。試設計一個算法,
給出滿足要求的代表就餐方案。
«編程任務:
對於給定的代表數和餐桌數以及餐桌容量,編程計算滿足要求的代表就餐方案。
«數據輸入:
由文件roundtable.in提供輸入數據。文件第1行有2 個正整數m和n,m表示單位數,n表
示餐桌數,1<=m<=150, 1<=n<=270。文件第2 行有m個正整數,分別表示每個單位的代表
數。文件第3 行有n個正整數,分別表示每個餐桌的容量。
«結果輸出:
程序運行結束時,將代表就餐方案輸出到文件roundtable.out中。如果問題有解,在文件第
1 行輸出1,否則輸出0。接下來的m行給出每個單位代表的就餐桌號。如果有多個滿足要
求的方案,只要輸出1 個方案。
輸入文件示例 輸出文件示例
roundtable.in
4 5
4 5 3 5
3 5 2 6 4 roundtable.out
1
1 2 4 5
1 2 3 4 5
2 4 5
1 2 3 4 5
【問題分析】
二分圖多重匹配問題,可以用最大流解決。
【建模方法】
建立二分圖,每個單位爲X集合中的頂點,每個餐桌爲Y集合中的頂點,增設附加源S和匯T。
1、從S向每個Xi頂點連接一條容量爲該單位人數的有向邊。
2、從每個Yi頂點向T連接一條容量爲該餐桌容量的有向邊。
3、X集合中每個頂點向Y集合中每個頂點連接一條容量爲1的有向邊。
求網絡最大流,如果最大流量等於所有單位人數之和,則存在解,否則無解。對於每個單位,從X集合對應點出發的所有滿流邊指向的Y集合的頂點就是該單位人員的安排情況(一個可行解)。
【建模分析】
對於一個二分圖,每個頂點可以有多個匹配頂點,稱這類問題爲二分圖多重匹配問題。X,Y集合之間的邊容量全部是1,保證兩個點只能匹配一次(一個餐桌上只能有一個單位的一個人),源匯的連邊限制了每個點匹配的個數。求出網絡最大流,如果流量等於X集合所有點與S邊容量之和,那麼則說明X集合每個點都有完備的多重匹配。
【問題另解】
貪心,更好的方法其實是貪心。首先把所有單位和餐桌按人數從大到小排序,一種適當的貪心策略就是對於每個單位,所有人每次儘量去剩餘容量較大的餐桌就坐。按照這種貪心策略,如果某時發現有人已經無法就坐,則無解。具體方法爲用線段樹維護餐桌的剩餘容量,按人數從多到少安排每個單位的人員,每次安排就是把容量餐桌前k大的餐桌人數減1(k爲該單位人數)。爲保證線段樹前k位時刻爲前k大,要維護第k與第k+1,k+2,…人數與第k相等的位置,減少第k大時要減少儘量靠後的,這樣才能保證單調。
#include<bits/stdc++.h>
#define MAXN 510
#define MAXM 100000
#define INF 0x3f3f3f3f
using namespace std;
struct Edge
{
int from, to, cap, flow, next;
};
Edge edge[MAXM];
int head[MAXN], cur[MAXN], edgenum;
int dist[MAXN];
bool vis[MAXN];
int N, M,ss,tt;
void init()
{
edgenum = 0;
memset(head, -1, sizeof(head));
}
void addEdge(int u, int v, int w)
{
Edge E1 = {u, v, w, 0, head[u]};
edge[edgenum] = E1;
head[u] = edgenum++;
Edge E2 = {v, u, 0, 0, head[v]};
edge[edgenum] = E2;
head[v] = edgenum++;
}
bool BFS(int s, int t)
{
queue<int> Q;
memset(dist, -1, sizeof(dist));
memset(vis, false, sizeof(vis));
dist[s] = 0;
vis[s] = true;
Q.push(s);
while(!Q.empty())
{
int u = Q.front();
Q.pop();
for(int i = head[u]; i != -1; i = edge[i].next)
{
Edge E = edge[i];
if(!vis[E.to] && E.cap > E.flow)
{
dist[E.to] = dist[u] + 1;
if(E.to == t) return true;
vis[E.to] = true;
Q.push(E.to);
}
}
}
return false;
}
int DFS(int x, int a, int t)
{
if(x == t || a == 0) return a;
int flow = 0, f;
for(int &i = cur[x]; i != -1; i = edge[i].next)
{
Edge &E = edge[i];
if(dist[E.to] == dist[x] + 1 && (f = DFS(E.to, min(a, E.cap - E.flow), t)) > 0)
{
edge[i].flow += f;
edge[i^1].flow -= f;
flow += f;
a -= f;
if(a == 0) break;
}
}
return flow;
}
int Maxflow(int s, int t)
{
int flow = 0;
while(BFS(s, t))
{
memcpy(cur, head, sizeof(head));
flow += DFS(s, INF, t);
}
return flow;
}
int T;
int n,m,t;
int main()
{
freopen("roundtable.in","r",stdin);
freopen("roundtable.out","w",stdout);
// scanf("%d", &T);
// while(T--)
// {
//
// }
int sum = 0;
int ss,tt;
scanf("%d%d", &m, &n);
ss=0,tt=m+n+1;
init();
for(int i=1;i<=m;i++)
{
scanf("%d",&t);
sum+=t;
addEdge(0,i,t);
for(int j=m+1;j<=n+m;j++)
{
addEdge(i,j,1);
}
}
for(int j=m+1;j<=n+m;j++)
{
scanf("%d",&t);
addEdge(j,tt,t);
}
if(Maxflow(ss, tt)<sum)
{
printf("%d\n",0);
}
else
{
printf("%d\n",1);
for(int i=1;i<=m;i++)
{
for(int j = head[i]; j != -1; j = edge[j].next)
{
if(edge[j].flow==1)
{
printf("%d ",edge[j].to-m);
}
}
printf("\n");
}
}
return 0;
}
最多不相交路徑
【問題分析】
第一問時LIS,動態規劃求解,第二問和第三問用網絡最大流解決。
【建模方法】
首先動態規劃求出F[i],表示以第i位爲開頭的最長上升序列的長度,求出最長上升序列長度K。
1、把序列每位i拆成兩個點
例題:
給定正整數序列x1,….. , xn 。
(1)計算其最長遞增子序列的長度s。
(2)計算從給定的序列中最多可取出多少個長度爲s的遞增子序列。
(3)如果允許在取出的序列中多次使用x1和xn,則從給定序列中最多可取出多少個長
度爲s的遞增子序列。
【問題分析】
第一問時LIS,動態規劃求解,第二問和第三問用網絡最大流解決。
【建模方法】
首先動態規劃求出F[i],表示以第i位爲開頭的最長上升序列的長度,求出最長上升序列長度K。
1、把序列每位i拆成兩個點
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MAXN 100010
#define MAXM 10000000+10
#define INF 0x3f3f3f3f
struct Edge
{
int from, to, cap, flow, next;
};
Edge edge[MAXM];
int head[MAXN], cur[MAXN], edgenum;
int dist[MAXN];
bool vis[MAXN];
int N, M,ss,tt;
void init()
{
edgenum = 0;
memset(head, -1, sizeof(head));
}
void addEdge(int u, int v, int w)
{
Edge E1 = {u, v, w, 0, head[u]};
edge[edgenum] = E1;
head[u] = edgenum++;
Edge E2 = {v, u, 0, 0, head[v]};
edge[edgenum] = E2;
head[v] = edgenum++;
}
bool BFS(int s, int t)
{
queue<int> Q;
memset(dist, -1, sizeof(dist));
memset(vis, false, sizeof(vis));
dist[s] = 0;
vis[s] = true;
Q.push(s);
while(!Q.empty())
{
int u = Q.front();
Q.pop();
for(int i = head[u]; i != -1; i = edge[i].next)
{
Edge E = edge[i];
if(!vis[E.to] && E.cap > E.flow)
{
dist[E.to] = dist[u] + 1;
if(E.to == t) return true;
vis[E.to] = true;
Q.push(E.to);
}
}
}
return false;
}
int DFS(int x, int a, int t)
{
if(x == t || a == 0) return a;
int flow = 0, f;
for(int &i = cur[x]; i != -1; i = edge[i].next)
{
Edge &E = edge[i];
if(dist[E.to] == dist[x] + 1 && (f = DFS(E.to, min(a, E.cap - E.flow), t)) > 0)
{
edge[i].flow += f;
edge[i^1].flow -= f;
flow += f;
a -= f;
if(a == 0) break;
}
}
return flow;
}
int Maxflow(int s, int t)
{
int flow = 0;
while(BFS(s, t))
{
memcpy(cur, head, sizeof(head));
flow += DFS(s, INF, t);
}
return flow;
}
int n;
int a[100005];
int dp[100005];
int main()
{
freopen("data.txt","r",stdin);
ios_base::sync_with_stdio(false);
cin >> n;
int s = 0;
int t = n+n+1;
for(int i=1;i<=n;i++)
{
cin >> a[i];
}
dp[1]=1;
for(int i=2;i<=n;i++)
{
dp[i]=1;
for(int j=1;j<i;j++)
{
if(a[i]>=a[j])
{
dp[i] = max(dp[i],dp[j]+1);
}
}
}
int ans = *max_element(dp+1,dp+1+n);
cout << ans<<endl;
init();
for(int i=1;i<=n;i++)
{
addEdge(i,i+n,1);
}
for(int i=1;i<=n;i++)
{
if(dp[i]==ans)
{
addEdge(s,i,1);
}
if(dp[i]==1)
{
addEdge(i+n,t,1);
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<i;j++)
{
if(a[i]>=a[j]&& dp[i] == dp[j]+1)
{
addEdge(i+n,j,1);
}
}
}
cout << Maxflow(s,t)<<endl;
init();
for(int i=1;i<=n;i++)
{
int fl=1;
if(i==1||i==n) fl=INF;
addEdge(i,i+n,fl);
}
for(int i=1;i<=n;i++)
{
if(dp[i]==ans)
{
if(i==n)
addEdge(s,i,INF);
else
addEdge(s,i,1);
}
if(dp[i]==1)
{
if(i==1)
addEdge(i+n,t,INF);
else
addEdge(i+n,t,1);
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<i;j++)
{
if(a[i]>=a[j]&& dp[i] == dp[j]+1)
{
addEdge(i+n,j,1);
}
}
}
cout << Maxflow(s,t)<<endl;
return 0;
}
二分圖點權最大獨立集
【問題分析】
二分圖點權最大獨立集,轉化爲最小割模型,從而用最大流解決。
【建模方法】
首先把棋盤黑白染色,使相鄰格子顏色不同,所有黑色格子看做二分圖X集合中頂點,白色格子看做Y集合頂點,建立附加源S匯T。
1、從S向X集合中每個頂點連接一條容量爲格子中數值的有向邊。
2、從Y集合中每個頂點向T連接一條容量爲格子中數值的有向邊。
3、相鄰黑白格子Xi,Yj之間從Xi向Yj連接一條容量爲無窮大的有向邊。
求出網絡最大流,要求的結果就是所有格子中數值之和減去最大流量。
【建模分析】
這是一個二分圖最大點權獨立集問題,就是找出圖中一些點,使得這些點之間沒有邊相連,這些點的權值之和最大。獨立集與覆蓋集是互補的,求最大點權獨立集可以轉化爲求最小點權覆蓋集(最小點權支配集)。最小點權覆蓋集問題可以轉化爲最小割問題解決。結論:最大點權獨立集 = 所有點權 - 最小點權覆蓋集 = 所有點權 - 最小割集 = 所有點權 - 網絡最大流。
對於一個網絡,除去冗餘點(不存在一條ST路徑經過的點),每個頂點都在一個從S到T的路徑上。割的性質就是不存在從S到T的路徑,簡單割可以認爲割邊關聯的非ST節點爲割點,而在二分圖網絡流模型中每個點必關聯到一個割點(否則一定還有增廣路,當前割不成立),所以一個割集對應了一個覆蓋集(支配集)。最小點權覆蓋集就是最小簡單割,求最小簡單割的建模方法就是把XY集合之間的變容量設爲無窮大,此時的最小割就是最小簡單割了。
有關二分圖最大點權獨立集問題,更多討論見《最小割模型在信息學競賽中的應用》作者胡伯濤。
例題:
«問題描述:
在一個有m*n 個方格的棋盤中,每個方格中有一個正整數。現要從方格中取數,使任
意2 個數所在方格沒有公共邊,且取出的數的總和最大。試設計一個滿足要求的取數算法。
«編程任務:
對於給定的方格棋盤,按照取數要求編程找出總和最大的數。
【問題分析】
二分圖點權最大獨立集,轉化爲最小割模型,從而用最大流解決。
【建模方法】
首先把棋盤黑白染色,使相鄰格子顏色不同,所有黑色格子看做二分圖X集合中頂點,白色格子看做Y集合頂點,建立附加源S匯T。
1、從S向X集合中每個頂點連接一條容量爲格子中數值的有向邊。
2、從Y集合中每個頂點向T連接一條容量爲格子中數值的有向邊。
3、相鄰黑白格子Xi,Yj之間從Xi向Yj連接一條容量爲無窮大的有向邊。
求出網絡最大流,要求的結果就是所有格子中數值之和減去最大流量。
【建模分析】
這是一個二分圖最大點權獨立集問題,就是找出圖中一些點,使得這些點之間沒有邊相連,這些點的權值之和最大。獨立集與覆蓋集是互補的,求最大點權獨立集可以轉化爲求最小點權覆蓋集(最小點權支配集)。最小點權覆蓋集問題可以轉化爲最小割問題解決。結論:最大點權獨立集 = 所有點權 - 最小點權覆蓋集 = 所有點權 - 最小割集 = 所有點權 - 網絡最大流。
對於一個網絡,除去冗餘點(不存在一條ST路徑經過的點),每個頂點都在一個從S到T的路徑上。割的性質就是不存在從S到T的路徑,簡單割可以認爲割邊關聯的非ST節點爲割點,而在二分圖網絡流模型中每個點必關聯到一個割點(否則一定還有增廣路,當前割不成立),所以一個割集對應了一個覆蓋集(支配集)。最小點權覆蓋集就是最小簡單割,求最小簡單割的建模方法就是把XY集合之間的變容量設爲無窮大,此時的最小割就是最小簡單割了。
有關二分圖最大點權獨立集問題,更多討論見《最小割模型在信息學競賽中的應用》作者胡伯濤。
#include<bits/stdc++.h>
#define MAXN 1010
#define MAXM 10000100
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 100005;
int mm[1005][1005];
int n,m;
int dir[4][2] = { {1,0},{-1,0},{0,1},{0,-1} };
int col[1005][1005];
struct Edge
{
int from, to, cap, flow, next;
};
Edge edge[MAXM];
int head[MAXN], cur[MAXN], edgenum;
int dist[MAXN];
bool vis[MAXN];
int N, M,ss,tt;
void init()
{
edgenum = 0;
memset(head, -1, sizeof(head));
}
void addEdge(int u, int v, int w)
{
Edge E1 = {u, v, w, 0, head[u]};
edge[edgenum] = E1;
head[u] = edgenum++;
Edge E2 = {v, u, 0, 0, head[v]};
edge[edgenum] = E2;
head[v] = edgenum++;
}
bool BFS(int s, int t)
{
queue<int> Q;
memset(dist, -1, sizeof(dist));
memset(vis, false, sizeof(vis));
dist[s] = 0;
vis[s] = true;
Q.push(s);
while(!Q.empty())
{
int u = Q.front();
Q.pop();
for(int i = head[u]; i != -1; i = edge[i].next)
{
Edge E = edge[i];
if(!vis[E.to] && E.cap > E.flow)
{
dist[E.to] = dist[u] + 1;
if(E.to == t) return true;
vis[E.to] = true;
Q.push(E.to);
}
}
}
return false;
}
int DFS(int x, int a, int t)
{
if(x == t || a == 0) return a;
int flow = 0, f;
for(int &i = cur[x]; i != -1; i = edge[i].next)
{
Edge &E = edge[i];
if(dist[E.to] == dist[x] + 1 && (f = DFS(E.to, min(a, E.cap - E.flow), t)) > 0)
{
edge[i].flow += f;
edge[i^1].flow -= f;
flow += f;
a -= f;
if(a == 0) break;
}
}
return flow;
}
int Maxflow(int s, int t)
{
int flow = 0;
while(BFS(s, t))
{
memcpy(cur, head, sizeof(head));
flow += DFS(s, INF, t);
}
return flow;
}
void dfs(int x,int y,int cl)
{
col[x][y]=cl;
for(int i=0;i<4;i++)
{
int xx = x+dir[i][0];
int yy = y+dir[i][1];
if(xx<1 || xx>n||yy<1||yy>m||col[xx][yy]) continue;
if(cl==1)
dfs(xx,yy,2);
else
dfs(xx,yy,1);
}
}
int getnum(int x,int y)
{
return (x-1)*m+y;
}
int main()
{
// freopen("data.txt","r",stdin);
// ios_base::sync_with_stdio(false);
// int T;
// scanf("%d",&T);
while(scanf("%d%d",&n,&m)==2)
{
int ans = 0;
memset(col,0,sizeof(col));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",&mm[i][j]);
ans+=mm[i][j];
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(!col[i][j])
{
dfs(i,j,1);
}
}
}
// for(int i=1;i<=n;i++)
// {
// for(int j=1;j<=m;j++)
// {
// cout << col[i][j]<<" ";
// }
// cout <<endl;
// }
init();
ss = 0;
tt = n*m+1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(col[i][j]==1)
{
addEdge(ss,getnum(i,j),mm[i][j]);
for(int d=0;d<4;d++)
{
int xx = i+dir[d][0];
int yy = j+dir[d][1];
if(xx<1 || xx>n || yy<1 || yy>m ||col[xx][yy]==1) continue;
addEdge(getnum(i,j),getnum(xx,yy),INF);
}
}
else
{
addEdge(getnum(i,j),tt,mm[i][j]);
}
}
}
printf("%d\n",ans-Maxflow(ss,tt));
}
return 0;
}
線性規劃網絡優化
【問題分析】
網絡優化問題,用最小費用最大流解決。
【建模方法】
把每天分爲二分圖兩個集合中的頂點Xi,Yi,建立附加源S匯T。
1、從S向每個Xi連一條容量爲ri,費用爲0的有向邊。
2、從每個Yi向T連一條容量爲ri,費用爲0的有向邊。
3、從S向每個Yi連一條容量爲無窮大,費用爲p的有向邊。
4、從每個Xi向Xi+1(i+1<=N)連一條容量爲無窮大,費用爲0的有向邊。
5、從每個Xi向Yi+m(i+m<=N)連一條容量爲無窮大,費用爲f的有向邊。
6、從每個Xi向Yi+n(i+n<=N)連一條容量爲無窮大,費用爲s的有向邊。
求網絡最小費用最大流,費用流值就是要求的最小總花費。
【建模分析】
這個問題的主要約束條件是每天的餐巾夠用,而餐巾的來源可能是最新購買,也可能是前幾天送洗,今天剛剛洗好的餐巾。每天用完的餐巾可以選擇送到快洗部或慢洗部,或者留到下一天再處理。
經過分析可以把每天要用的和用完的分離開處理,建模後就是二分圖。二分圖X集合中頂點Xi表示第i天用完的餐巾,其數量爲ri,所以從S向Xi連接容量爲ri的邊作爲限制。Y集合中每個點Yi則是第i天需要的餐巾,數量爲ri,與T連接的邊容量作爲限制。每天用完的餐巾可以選擇留到下一天(Xi->Xi+1),不需要花費,送到快洗部(Xi->Yi+m),費用爲f,送到慢洗部(Xi->Yi+n),費用爲s。每天需要的餐巾除了剛剛洗好的餐巾,還可能是新購買的(S->Yi),費用爲p。
在網絡上求出的最小費用最大流,滿足了問題的約束條件(因爲在這個圖上最大流一定可以使與T連接的邊全部滿流,其他邊只要有可行流就滿足條件),而且還可以保證總費用最小,就是我們的優化目標。
例題:
題目描述 Description
一個餐廳在相繼的 N 天裏,每天需用的餐巾數不盡相同。假設第 i 天需要 ri塊餐巾(i=1,2,…,N)。餐廳可以購買新的餐巾,每塊餐巾的費用爲 p 分;或者把舊餐巾送到快洗部,洗一塊需 m 天,其費用爲 f 分;或者送到慢洗部,洗一塊需 n 天(n>m),其費用爲 s
#include<iostream>
#include<cstdio>
#define inf 0x7fffffff
#define T 2001
using namespace std;
int cnt=1,day,p,m,f,n,s,ans;
int from[2005],q[2005],dis[2005],head[2005];
bool inq[2005];
struct data{int from,to,next,v,c;}e[1000001];
void ins(int u,int v,int w,int c)
{
cnt++;
e[cnt].from=u;e[cnt].to=v;
e[cnt].v=w;e[cnt].c=c;
e[cnt].next=head[u];head[u]=cnt;
}
void insert(int u,int v,int w,int c)
{ins(u,v,w,c);ins(v,u,0,-c);}
bool spfa()
{
for(int i=0;i<=T;i++)dis[i]=inf;
int t=0,w=1,i,now;
dis[0]=q[0]=0;inq[0]=1;
while(t!=w)
{
now=q[t];t++;if(t==2001)t=0;
for(int i=head[now];i;i=e[i].next)
{
if(e[i].v&&dis[e[i].to]>dis[now]+e[i].c)
{
from[e[i].to]=i;
dis[e[i].to]=dis[now]+e[i].c;
if(!inq[e[i].to])
{
inq[e[i].to]=1;
q[w++]=e[i].to;
if(w==2001)w=0;
}
}
}
inq[now]=0;
}
if(dis[T]==inf)return 0;return 1;
}
void mcf()
{
int i,x=inf;
i=from[T];
while(i)
{
x=min(e[i].v,x);
i=from[e[i].from];
}
i=from[T];
while(i)
{
e[i].v-=x;
e[i^1].v+=x;
ans+=x*e[i].c;
i=from[e[i].from];
}
}
int main()
{
scanf("%d%d%d%d%d%d",&day,&p,&m,&f,&n,&s);
for(int i=1;i<=day;i++)
{
if(i+1<=day)insert(i,i+1,inf,0);
if(i+m<=day)insert(i,day+i+m,inf,f);
if(i+n<=day)insert(i,day+i+n,inf,s);
insert(0,day+i,inf,p);
}
int x;
for(int i=1;i<=day;i++)
{
scanf("%d",&x);
insert(0,i,x,0);
insert(day+i,T,x,0);
}
while(spfa())mcf();
printf("%d",ans);
return 0;
}
最長不相交路徑
【問題分析】
求最長兩條不相交路徑,用最大費用最大流解決。
【建模方法】
把第i個城市拆分成兩個頂點
例題:
問題描述:
給定一張航空圖,圖中頂點代表城市,邊代表 2 城市間的直通航線。現要求找出一條滿足下述限制條件的且途經城市最多的旅行路線。
1 從最西端城市出發,單向從西向東途經若干城市到達最東端城市,然後再單向從東
向西飛回起點(可途經若干城市)。
2 除起點城市外,任何城市只能訪問 1 次。
編程任務:
對於給定的航空圖,試設計一個算法找出一條滿足要求的最佳航空旅行路線。
數據輸入:
第 1 行有 2 個正整數 N 和 V , N 表示城市數, N<100 , V 表示直飛航線數。接下來的 N 行中每一行是一個城市名,可乘飛機訪問這些城市。城市名出現的順序是從西向東。也就是說,設 i,j 是城市表列中城市出現的順序,當 i>j 時,表示城市 i 在城市 j 的東邊,而且不會有 2 個城市在同一條經線上。城市名是一個長度不超過 15 的字符串,串中的字符可以是字母或阿拉伯數字。例如, AGR34 或 BEL4 。
再接下來的 V 行中,每行有 2 個城市名,中間用空格隔開,如 city1 city2 表示 city1到 city2 有一條直通航線,從 city2 到 city1 也有一條直通航線。
結果輸出:
輸出第 1 行是旅行路線中所訪問的城市總數 M 。接下來的 M+1 行是旅行路線的城市名,每行寫 1 個城市名。首先是出發城市名,然後按訪問順序列出其它城市名。注意,最後 1 行(終點城市)的城市名必然是出發城市名。如果問題無解,則輸出“ No Solution! ” 。
輸入文件示例:
8 9
Vancouver
Yellowknife
Edmonton
Calgary
Winnipeg
Toronto
Montreal
Halifax
Vancouver Edmonton
Vancouver Calgary
Calgary Winnipeg
Winnipeg Toronto
Toronto Halifax
Montreal Halifax
Edmonton Montreal
Edmonton Yellowknife
Edmonton Calgary
輸出文件示例:
7
Va
ncouver
Edmonton
Montreal
Halifax
To
ronto
Winnipeg
Calgary
Va
ncouver
【問題分析】
求最長兩條不相交路徑,用最大費用最大流解決。
【建模方法】
把第i個城市拆分成兩個頂點
/*
* Problem: 線性規劃與網絡流24題 #11 航空路線問題
* Author: Guo Jiabao
* Time: 2009.6.28 16:54
* State: Solved
* Memo: 最大費用最大流
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
using namespace std;
const int MAXN=1001,MAXM=MAXN*4*2,INF=~0U>>1;
struct Queue
{
int Q[MAXN],head,tail,size;
bool inq[MAXN];
void init()
{
memset(inq,0,sizeof(inq));
head = size =0; tail = -1;
}
void ins(int p)
{
size++;
if (++tail == MAXN) tail = 0;
Q[tail] = p;
inq[p]=true;
}
int pop()
{
size--;
int p=Q[head];
if (++head == MAXN) head = 0;
inq[p]=false;
return p;
}
}Q;
struct edge
{
edge *next,*op;
int t,c,v;
}*V[MAXN],ES[MAXM],*fe[MAXN];
char City[MAXN][16];
int N,M,S,T,EC,Ans,Costflow;
int dist[MAXN],ft[MAXN];
inline void addedge(int a,int b,int c,int v)
{
ES[++EC].next = V[a]; V[a]=ES+EC; V[a]->t=b; V[a]->c=c; V[a]->v=v;
ES[++EC].next = V[b]; V[b]=ES+EC; V[b]->t=a; V[b]->c=0; V[b]->v=-v;
V[a]->op = V[b]; V[b]->op = V[a];
}
int getcity(char *s)
{
for (int i=1;i<=N;i++)
if (strcmp(s,City[i])==0)
return i;
return -1;
}
void init()
{
int i,a,b;
char name[16];
freopen("airl.in","r",stdin);
freopen("airl.out","w",stdout);
scanf("%d%d",&N,&M);
for (i=1;i<=N;i++)
{
scanf("%s",City[i]);
if (i==1 || i==N)
addedge(i,i+N,2,-1);
else
addedge(i,i+N,1,-1);
}
for (i=1;i<=M;i++)
{
scanf("%s",name);a=getcity(name);
scanf("%s",name);b=getcity(name);
if (a<b)
addedge(a+N,b,1,0);
else
addedge(b+N,a,1,0);
}
S=1; T=N+N;
}
bool SPFA()
{
int i,j;
for (i=S;i<=T;i++)
dist[i]=INF;
dist[S]=0;
Q.ins(S);
while (Q.size)
{
i=Q.pop();
for (edge *e=V[i];e;e=e->next)
{
j=e->t;
if (e->c && dist[i] + e->v < dist[j])
{
dist[j] = dist[i] + e->v;
ft[j] = i;
fe[j] = e;
if (!Q.inq[j])
Q.ins(j);
}
}
}
return dist[T]!=INF;
}
void Augment()
{
int i,delta=INF;
for (i=T;i!=S;i=ft[i])
if (fe[i]->c < delta)
delta = fe[i]->c;
for (i=T;i!=S;i=ft[i])
{
fe[i]->c -= delta;
fe[i]->op->c += delta;
Costflow += fe[i]->v * delta;
}
}
void SPFAFlow()
{
Q.init();
while (SPFA())
Augment();
}
int main()
{
init();
SPFAFlow();
Costflow =-Costflow;
Costflow -= 2;
if (ES[1].c!=0)
Costflow = 1;
printf("%d\n",Costflow);
return 0;
}
最小轉移代價
【問題分析】
求一個狀態到另一個狀態變換的最少費用,最短路徑問題。
【建模方法】
軟件的狀態用二進制位表示,第i位爲第i個錯誤是否存在。把每個狀態看做一個頂點,一個狀態應用一個補丁到達另一狀態,連接一條權值爲補丁時間的有向邊。求從初始狀態到目標狀態的最短路徑即可。
例題:
T 公司發現其研製的一個軟件中有 n 個錯誤, 隨即爲該軟件發放了一批共 m 個補丁程
序。 每一個補丁程序都有其特定的適用環境, 某個補丁只有在軟件中包含某些錯誤而同時又
不包含另一些錯誤時纔可以使用。一個補丁在排除某些錯誤的同時, 往往會加入另一些錯誤。
換句話說, 對於每一個補丁 i, 都有 2 個與之相應的錯誤集合 B1[i]和 B2[i],使得僅當軟件
包含 B1[i]中的所有錯誤, 而不包含 B2[i]中的任何錯誤時, 纔可以使用補丁 i。 補丁 i 將修復
軟件中的某些錯誤 F1[i], 而同時加入另一些錯誤 F2[i]。 另外, 每個補丁都耗費一定的時間。
試設計一個算法, 利用 T 公司提供的 m 個補丁程序將原軟件修復成一個沒有錯誤的軟
件, 並使修復後的軟件耗時最少。
輸入文件示例
input.txt
3 3
1 000 00-
1 00- 0-+
2 0– -++
輸出文件示例
output.txt
8
【分析】
sm網絡流24題,怎麼什麼題都有。
明明就是一道最短路,就spfa就好了。。。
最小轉移代價,但是沒什麼約束,所以用不着流,直接費用跑過。。
【問題分析】
求一個狀態到另一個狀態變換的最少費用,最短路徑問題。
【建模方法】
軟件的狀態用二進制位表示,第i位爲第i個錯誤是否存在。把每個狀態看做一個頂點,一個狀態應用一個補丁到達另一狀態,連接一條權值爲補丁時間的有向邊。求從初始狀態到目標狀態的最短路徑即可。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<cmath>
#include<map>
using namespace std;
#define Maxm 110
#define Maxn 1100000
#define INF 0xfffffff
int w[Maxm],b1[Maxm],b2[Maxm],f1[Maxm],f2[Maxm];
int dis[Maxn];
bool inq[Maxn];
queue<int > q;
int st,ed,n,m;
void spfa()
{
while(!q.empty()) q.pop();
memset(dis,63,sizeof(dis));
memset(inq,0,sizeof(inq));
q.push(st);dis[st]=0;inq[st]=1;
while(!q.empty())
{
int x=q.front();
for(int i=1;i<=m;i++) if((x&b1[i])==b1[i]&&(x&b2[i])==0)
{
int y=x-(x&f1[i]);
y|=f2[i];
if(dis[y]>dis[x]+w[i])
{
dis[y]=dis[x]+w[i];
if(!inq[y])
{
q.push(y);
inq[y]=1;
}
}
}
q.pop();inq[x]=0;
}
}
char s[30];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d",&w[i]);
scanf("%s",s);
b1[i]=b2[i]=f1[i]=f2[i]=0;
for(int j=0;j<n;j++)
if(s[j]=='+') b1[i]+=1<<j;
else if(s[j]=='-') b2[i]+=1<<j;
scanf("%s",s);
for(int j=0;j<n;j++)
if(s[j]=='-') f1[i]+=1<<j;
else if(s[j]=='+') f2[i]+=1<<j;
}
st=(1<<n)-1;ed=0;
spfa();
if(dis[ed]>=INF-100000) printf("0\n");
else printf("%d\n",dis[ed]);
return 0;
}
網絡判定
【問題分析】
分層圖網絡流問題,枚舉答案,構造網絡流判定。
【建模方法】
首先判斷從地球到月球是否存在一條路線,如果不存在那麼無解,否則把每個太空站按照每天拆分成d個點,
/*
* Problem: 線性規劃與網絡流24題 #13 星際轉移問題
* Author: Guo Jiabao
* Time: 2009.6.28 19:36
* State: Solved
* Memo: 分層圖網絡最大流 枚舉答案
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
using namespace std;
const int MAXN=20*50,MAXM=MAXN*20,INF=~0U>>1;
struct UFS
{
int f[MAXN];
void init(int N)
{
for (int i=1;i<=N;i++)
f[i]=i;
}
int getroot(int a)
{
int r=a,t;
while (r!=f[r]) r=f[r];
while (a!=r)
{
t=f[a];
f[a]=r;
a=t;
}
return r;
}
void merge(int a,int b)
{
f[getroot(a)]=getroot(b);
}
bool judge(int a,int b)
{
return getroot(a)==getroot(b);
}
}U;
struct ship
{
int c,len,p[21];
}Ship[21];
struct edge
{
edge *next,*op;
int t,c;
}*V[MAXN],*P[MAXN],ES[MAXM],*Stae[MAXN];
int N,M,K,S,T,EC,Ans,Maxflow;
int Lv[MAXN],Stap[MAXN];
inline void addedge(int a,int b,int c)
{
ES[++EC].next = V[a]; V[a]=ES+EC; V[a]->t=b; V[a]->c=c;
ES[++EC].next = V[b]; V[b]=ES+EC; V[b]->t=a; V[b]->c=0;
V[a]->op = V[b]; V[b]->op = V[a];
}
void init()
{
int i,j,c;
freopen("home.in","r",stdin);
freopen("home.out","w",stdout);
scanf("%d%d%d",&N,&M,&K);
S=0; T=MAXN-1;
U.init(N+2);
for (i=1;i<=M;i++)
{
scanf("%d%d",&Ship[i].c,&Ship[i].len);
for (j=1;j<=Ship[i].len;j++)
{
scanf("%d",&c);
if (c==0)
c=N+2;
if (c==-1)
c=N+1;
Ship[i].p[j] = c;
if (j>1)
U.merge(Ship[i].p[j-1],c);
}
}
N+=2;
}
bool Dinic_Label()
{
int head,tail,i,j;
Stap[head=tail=0]=S;
memset(Lv,-1,sizeof(Lv));
Lv[S]=0;
while (head<=tail)
{
i=Stap[head++];
for (edge *e=V[i];e;e=e->next)
{
j=e->t;
if (e->c && Lv[j]==-1)
{
Lv[j] = Lv[i]+1;
if (j==T)
return true;
Stap[++tail] = j;
}
}
}
return false;
}
void Dinic_Augment()
{
int i,j,delta,Stop;
for (i=S;i<=T;i++)
P[i] = V[i];
Stap[Stop=1]=S;
while (Stop)
{
i=Stap[Stop];
if (i!=T)
{
for (;P[i];P[i]=P[i]->next)
if (P[i]->c && Lv[i] + 1 == Lv[j=P[i]->t])
break;
if (P[i])
{
Stap[++Stop] = j;
Stae[Stop] = P[i];
}
else
Stop--,Lv[i]=-1;
}
else
{
delta = INF;
for (i=Stop;i>=2;i--)
if (Stae[i]->c < delta)
delta = Stae[i]->c;
Maxflow += delta;
for (i=Stop;i>=2;i--)
{
Stae[i]->c -= delta;
Stae[i]->op->c += delta;
if (Stae[i]->c==0)
Stop = i-1;
}
}
}
}
void Dinic()
{
while (Dinic_Label())
Dinic_Augment();
}
inline int Point(int p,int d)
{
return d * N + p;
}
void solve()
{
int Day,i,a,b;
addedge(S,Point(N,0),INF);
addedge(Point(N-1,0),T,INF);
for (Day = 1;Maxflow < K;Day++)
{
addedge(S,Point(N,Day),INF);
addedge(Point(N-1,Day),T,INF);
for (i=1;i<=N;i++)
addedge(Point(i,Day-1),Point(i,Day),INF);
for (i=1;i<=M;i++)
{
a = Ship[i].p[ (Day - 1) % Ship[i].len + 1 ];
b = Ship[i].p[ Day % Ship[i].len + 1 ];
addedge(Point(a,Day-1),Point(b,Day),Ship[i].c);
}
Dinic();
}
Ans = Day - 1;
}
int main()
{
init();
if (U.judge(N-1,N))
solve();
else
Ans = 0;
printf("%d\n",Ans);
return 0;
}
分層圖最短路徑
【問題分析】
分層圖最短路徑問題。
【建模方法】
用P位二進制表示當前獲得的鑰匙狀態,建立2^P層圖。每層圖表示在當前鑰匙狀態下的地圖,每獲得一把鑰匙進入新的一層,BFS求最短路即可。
1944 年,特種兵麥克接到國防部的命令,要求立即趕赴太平洋上的一個孤島,營救被
敵軍俘虜的大兵瑞恩。瑞恩被關押在一個迷宮裏,迷宮地形複雜,但幸好麥克得到了迷宮的
地形圖。迷宮的外形是一個長方形,其南北方向被劃分爲N 行,東西方向被劃分爲M列,
於是整個迷宮被劃分爲N×M 個單元。每一個單元的位置可用一個有序數對(單元的行號,
單元的列號)來表示。南北或東西方向相鄰的2 個單元之間可能互通,也可能有一扇鎖着的
門,或者是一堵不可逾越的牆。迷宮中有一些單元存放着鑰匙,並且所有的門被分成P類,
打開同一類的門的鑰匙相同,不同類門的鑰匙不同。
大兵瑞恩被關押在迷宮的東南角,即(N,M)單元裏,並已經昏迷。迷宮只有一個入口,
在西北角。也就是說,麥克可以直接進入(1,1)單元。另外,麥克從一個單元移動到另一個
相鄰單元的時間爲1,拿取所在單元的鑰匙的時間以及用鑰匙開門的時間可忽略不計。
«編程任務:
試設計一個算法,幫助麥克以最快的方式到達瑞恩所在單元,營救大兵瑞恩。
分層圖最短路徑問題。
【建模方法】
用P位二進制表示當前獲得的鑰匙狀態,建立2^P層圖。每層圖表示在當前鑰匙狀態下的地圖,每獲得一把鑰匙進入新的一層,BFS求最短路即可。
#include<bits/stdc++.h>
#define go(i,a,b) for(int i=a;i<=b;i++)
#define fo(i,a,x) for(int i=a[x];i>-1;i=e[i].next)
#define mem(a,b) memset(a,b,sizeof(a))
#define P 3000
#define inf 0x3f3f3f3f
using namespace std;const int N=203;
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
int n,m,p,f,S[N][N],id[N][N],key[N],head[N],k=0,s,t,dis[P][N];
struct E{int v,next,rar;}e[N*N*4];bool inq[P][N];
void ADD(int u,int v,int rar){e[k]=(E){v,head[u],rar};head[u]=k++;}
struct G{int x,y;};
int spfa()
{
mem(dis,0x3f);dis[1][1]=0;inq[1][1]=1;
queue<G>q;q.push((G){1,1});
while(!q.empty())
{
G U=q.front();q.pop();inq[U.x][U.y]=0;
dis[U.x|key[U.y]][U.y]=min(dis[U.x|key[U.y]][U.y],dis[U.x][U.y]);
U.x|=key[U.y];
fo(i,head,U.y)if(U.x%(e[i].rar<<1)>=e[i].rar)
{
int v=e[i].v;
if(dis[U.x][v]>dis[U.x][U.y]+1)
{
dis[U.x][v]=dis[U.x][U.y]+1;
if(!inq[U.x][v])
{
inq[U.x][v]=1;
q.push((G){U.x,v});
}
}
}
}
int ret=inf;go(i,1,P-1)ret=min(ret,dis[i][t]);
if(ret==inf)ret=-1;return ret;
}
int main()
{
int k,a,b,c;mem(S,-1);mem(head,-1);
scanf("%d%d%d%d",&n,&m,&p,&f);
go(i,1,n)go(j,1,m)id[i][j]=++t;
go(i,1,f){scanf("%d%d",&a,&b);a=id[a][b];
scanf("%d%d",&b,&c);b=id[b][c];
scanf("%d",&c);S[a][b]=S[b][a]=c;
if(c)ADD(a,b,1<<c),ADD(b,a,1<<c);}
go(i,1,n)go(j,1,m)
{
a=id[i][j];go(k,0,3)
{
int x=i+dx[k],y=j+dy[k];b=id[x][y];
if(S[a][b]==-1&&b)ADD(a,b,1);
}
}
scanf("%d",&f);
go(i,1,f)scanf("%d%d%d",&a,&b,&c),key[id[a][b]]|=(1<<c);
printf("%d\n",spfa());
return 0;
}
最大權不相交路徑
不相交其實就是拆點,拆點連流量爲1,費用爲其權值的邊,這樣保證了點只連一次,然後跑費用流.
問題描述:
給定一個由n 行數字組成的數字梯形如下圖所示。梯形的第一行有m 個數字。從梯形
的頂部的m 個數字開始,在每個數字處可以沿左下或右下方向移動,形成一條從梯形的頂
至底的路徑。
規則1:從梯形的頂至底的m條路徑互不相交。
規則2:從梯形的頂至底的m條路徑僅在數字結點處相交。
規則3:從梯形的頂至底的m條路徑允許在數字結點相交或邊相交。
對於給定的數字梯形,分別按照規則1,規則2,和規則3 計算出從梯形的頂至底的m
條路徑,使這m條路徑經過的數字總和最大
【問題分析】
求圖的最大權不相交路徑及其變種,用費用最大流解決。
【建模方法】
規則(1)
把梯形中每個位置抽象爲兩個點
/*
* Problem: 線性規劃與網絡流24題 #16 digit
* Author: Guo Jiabao
* Time: 2009.6.29 16:38
* State: Solved
* Memo: 最大費用最大流
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
using namespace std;
const int MAXL=41,MAXN=1001*2,MAXM=MAXN*2*2,INF=~0U>>1;
struct Queue
{
int Q[MAXN],head,tail,size;
bool inq[MAXN];
void init()
{
memset(inq,0,sizeof(inq));
head = size =0; tail = -1;
}
void ins(int p)
{
size++;
if (++tail == MAXN) tail = 0;
Q[tail] = p;
inq[p]=true;
}
int pop()
{
size--;
int p=Q[head];
if (++head == MAXN) head = 0;
inq[p]=false;
return p;
}
}Q;
struct edge
{
edge *next,*op;
int t,c,v;
}*V[MAXN],ES[MAXM],*fe[MAXN];
int N,M,C,S,T,EC,Ans,Costflow;
int Poi[MAXL][MAXL],Value[MAXN],dist[MAXN],ft[MAXN];
inline void addedge(int a,int b,int c,int v)
{
ES[++EC].next = V[a]; V[a]=ES+EC; V[a]->t=b; V[a]->c=c; V[a]->v=v;
ES[++EC].next = V[b]; V[b]=ES+EC; V[b]->t=a; V[b]->c=0; V[b]->v=-v;
V[a]->op = V[b]; V[b]->op = V[a];
}
void init()
{
int i,j;
freopen("digit.in","r",stdin);
freopen("digit.out","w",stdout);
scanf("%d%d",&M,&N);
for (i=1;i<=N;i++)
for (j=1;j<=M+i-1;j++)
{
Poi[i][j] = ++C;
scanf("%d",&Value[C]);
}
}
bool SPFA()
{
int i,j;
for (i=S;i<=T;i++)
dist[i]=-INF;
dist[S]=0;
Q.ins(S);
while (Q.size)
{
i=Q.pop();
for (edge *e=V[i];e;e=e->next)
{
j=e->t;
if (e->c && dist[i] + e->v > dist[j])
{
dist[j] = dist[i] + e->v;
ft[j] = i;
fe[j] = e;
if (!Q.inq[j])
Q.ins(j);
}
}
}
return dist[T]!=-INF;
}
void Augment()
{
int i,delta=INF;
for (i=T;i!=S;i=ft[i])
if (fe[i]->c < delta)
delta = fe[i]->c;
for (i=T;i!=S;i=ft[i])
{
fe[i]->c -= delta;
fe[i]->op->c += delta;
Costflow += fe[i]->v * delta;
}
}
void SPFAFlow()
{
Q.init();
while (SPFA())
Augment();
}
void solve1()
{
int i,j;
S=0;T=C+C+1;EC=-1;
Costflow = 0;
memset(V,0,sizeof(V));
for (i=1;i<=C;i++)
addedge(i,i+C,1,Value[i]);
for (i=1;i<=M;i++)
addedge(S,Poi[1][i],1,0);
for (i=1;i<=M+N-1;i++)
addedge(Poi[N][i]+C,T,1,0);
for (i=1;i<N;i++)
for (j=1;j<=M+i-1;j++)
{
addedge(Poi[i][j]+C,Poi[i+1][j],1,0);
addedge(Poi[i][j]+C,Poi[i+1][j+1],1,0);
}
SPFAFlow();
}
void solve2()
{
int i,j;
S=0;T=C+1;EC=-1;
Costflow = 0;
memset(V,0,sizeof(V));
for (i=1;i<=M;i++)
addedge(S,Poi[1][i],1,0);
for (i=1;i<=M+N-1;i++)
addedge(Poi[N][i],T,INF,Value[Poi[N][i]]);
for (i=1;i<N;i++)
for (j=1;j<=M+i-1;j++)
{
addedge(Poi[i][j],Poi[i+1][j],1,Value[Poi[i][j]]);
addedge(Poi[i][j],Poi[i+1][j+1],1,Value[Poi[i][j]]);
}
SPFAFlow();
}
void solve3()
{
int i,j;
S=0;T=C+1;EC=-1;
Costflow = 0;
memset(V,0,sizeof(V));
for (i=1;i<=M;i++)
addedge(S,Poi[1][i],1,0);
for (i=1;i<=M+N-1;i++)
addedge(Poi[N][i],T,INF,Value[Poi[N][i]]);
for (i=1;i<N;i++)
for (j=1;j<=M+i-1;j++)
{
addedge(Poi[i][j],Poi[i+1][j],INF,Value[Poi[i][j]]);
addedge(Poi[i][j],Poi[i+1][j+1],INF,Value[Poi[i][j]]);
}
SPFAFlow();
}
void solve()
{
solve1();
printf("%d\n",Costflow);
solve2();
printf("%d\n",Costflow);
solve3();
printf("%d\n",Costflow);
}
int main()
{
init();
solve();
return 0;
}
網絡費用流量
【問題分析】
費用流問題。
【建模方法】
把所有倉庫看做二分圖中頂點Xi,所有零售商店看做二分圖中頂點Yi,建立附加源S匯T。
1、從S向每個Xi連一條容量爲倉庫中貨物數量ai,費用爲0的有向邊。
2、從每個Yi向T連一條容量爲商店所需貨物數量bi,費用爲0的有向邊。
3、從每個Xi向每個Yj連接一條容量爲無窮大,費用爲cij的有向邊。
求最小費用最大流,最小費用流值就是最少運費,求最大費用最大流,最大費用流值就是最多運費。
【建模分析】
把每個倉庫想象成一箇中轉站,由源點運來ai單位貨物,運費爲0,每個商店也爲一箇中轉站,運向目標匯點bi單位貨物。每個倉庫和零售商店之間有一條道路,容量爲無窮大,費用爲單位運費cij。求從源點到匯點的費用流,就是運費。
W 公司有m個倉庫和n 個零售商店。第i 個倉庫有ai 個單位的貨物;第j 個零售商店
需要bj個單位的貨物。貨物供需平衡,即 sum(si)=sum(bj)
。從第i 個倉庫運送每單位貨物到
第j 個零售商店的費用爲cij 。試設計一個將倉庫中所有貨物運送到零售商店的運輸方案,
使總運輸費用最少。
編程任務:
對於給定的m 個倉庫和n 個零售商店間運送貨物的費用,計算最優運輸方案和最差運輸方案。
輸入描述 Input Description
的第1行有2 個正整數m和n,分別表示倉庫數和
零售商店數。接下來的一行中有m個正整數ai ,1≤i≤m,表示第i個倉庫有ai 個單位的貨
物。再接下來的一行中有n個正整數bj ,1≤j≤n,表示第j個零售商店需要bj 個單位的貨
物。接下來的m行,每行有n個整數,表示從第i 個倉庫運送每單位貨物到第j個零售商店
的費用cij 。
輸出描述 Output Description
將計算出的最少運輸費用和最多運輸費用輸出
樣例輸入 Sample Input
2 3
220 280
170 120 210
77 39 105
150 186 122
樣例輸出 Sample Output
48500
69140
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 10000;
const int MAXM = 100000;
const int INF = 0x3f3f3f3f;
struct Edge
{
int to,next,cap,flow,cost;
} edge[MAXM];
int head[MAXN],tol;
int pre[MAXN],dis[MAXN];
bool vis[MAXN];
int N;//節點總個數,節點編號從0~N-1
void init(int n)
{
N = n;
tol = 0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int cap,int cost)
{
edge[tol].to = v;
edge[tol].cap = cap;
edge[tol].cost = cost;
edge[tol].flow = 0;
edge[tol].next = head[u];
head[u] = tol++;
edge[tol].to = u;
edge[tol].cap = 0;
edge[tol].cost = -cost;
edge[tol].flow = 0;
edge[tol].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);
while(!q.empty())
{
int u = q.front();
q.pop();
vis[u] = false;
for(int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if(edge[i].cap > edge[i].flow &&
dis[v] > dis[u] + edge[i].cost )
{
dis[v] = dis[u] + edge[i].cost;
pre[v] = i;
if(!vis[v])
{
vis[v] = true;
q.push(v);
}
}
}
}
if(pre[t] == -1)return false;
else return true;
}
//返回的是最大流,cost存的是最小費用
int minCostMaxflow(int s,int t,int &cost)
{
int flow = 0;
cost = 0;
while(spfa(s,t))
{
int Min = INF;
for(int i = pre[t]; 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[t]; i != -1; i = pre[edge[i^1].to])
{
edge[i].flow += Min;
edge[i^1].flow -= Min;
cost += edge[i].cost * Min;
}
flow += Min;
}
return flow;
}
int a[105];
int b[105];
int c[105][105];
int T;
int n,m,t;
int main()
{
freopen("tran.in","r",stdin);
freopen("tran.out","w",stdout);
// freopen("data.txt","r",stdin);
// scanf("%d", &T);
// while(T--)
// {
//
// }
int sum = 0;
int ss,tt;
scanf("%d%d", &m, &n);
ss=0,tt=m+n+1;
init(500);
for(int i=1;i<=m;i++)
{
scanf("%d",&a[i]);
sum+=a[i];
addedge(0,i,a[i],0);
// for(int j=k+1;j<=k+m;j++)
// {
// addEdge(i,j,1);
// }
}
for(int j=m+1;j<=n+m;j++)
{
scanf("%d",&b[j]);
addedge(j,tt,b[j],0);
// for(int i=0;i<t;i++)
// {
// scanf("%d",&w);
// addEdge(w,j,1);
// }
}
for(int i = 1 ;i <= m; i++)
{
for(int j = 1;j <= n; j++)
{
scanf("%d",&c[i][j]);
addedge(i,j+m,INF,c[i][j]);
}
}
int cost =0 ;
minCostMaxflow(ss, tt,cost);
printf("%d\n",cost);
init(500);
for(int i=1;i<=m;i++)
{
addedge(0,i,a[i],0);
// for(int j=k+1;j<=k+m;j++)
// {
// addEdge(i,j,1);
// }
}
for(int j=m+1;j<=n+m;j++)
{
addedge(j,tt,b[j],0);
// for(int i=0;i<t;i++)
// {
// scanf("%d",&w);
// addEdge(w,j,1);
// }
}
for(int i = 1 ;i <= m; i++)
{
for(int j = 1;j <= n; j++)
{
addedge(i,j+m,INF,-c[i][j]);
}
}
minCostMaxflow(ss, tt,cost);
printf("%d\n",-cost);
// if(Maxflow(ss, tt)<sum)
// {
// printf("NoSolution!\n");
// }
// else
// {
//
// for(int i=1;i<=k;i++)
// {
// printf("%d:",i);
// for(int j = head[i]; j != -1; j = edge[j].next)
// {
// if(edge[j].flow==1)
// {
// printf("%d ",edge[j].to-k);
//
// }
// }
// printf("\n");
// }
//
//
// }
return 0;
}
二分圖最佳匹配
【問題分析】
二分圖最佳匹配問題,可以費用流解決(或KM算法)。
【建模方法】
把所有人看做二分圖中頂點Xi,所有工作看做二分圖中頂點Yi,建立附加源S匯T。
1、從S向每個Xi連一條容量爲1,費用爲0的有向邊。
2、從每個Yi向T連一條容量爲1,費用爲0的有向邊。
3、從每個Xi向每個Yj連接一條容量爲無窮大,費用爲Cij的有向邊。
求最小費用最大流,最小費用流值就是最少運費,求最大費用最大流,最大費用流值就是最多運費。
【建模分析】
二分圖最佳匹配建模方法爲費用流模型。
題目描述 Description
有n件工作要分配給n個人做。第i 個人做第j 件工作產生的效益爲ij c 。試設計一個將
n件工作分配給n個人做的分配方案,使產生的總效益最大。
«編程任務:
對於給定的n件工作和n個人,計算最優分配方案和最差分配方案。
輸入描述 Input Description
第1 行有1 個正整數n,表示有n件工作要分配給n 個人做。接下來的n 行中,每行有n 個整數 cij ,1≤i≤n,1≤j≤n,表示第i 個人做第j件工作產生的效益爲cij
輸出描述 Output Description
將計算出的最小總效益和最大總效益輸出
樣例輸入 Sample Input
5
2 2 2 1 2
2 3 1 2 4
2 0 1 1 1
2 3 4 3 3
3 2 1 2 1
樣例輸出 Sample Output
5
14
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 10000;
const int MAXM = 100000;
const int INF = 0x3f3f3f3f;
struct Edge
{
int to,next,cap,flow,cost;
} edge[MAXM];
int head[MAXN],tol;
int pre[MAXN],dis[MAXN];
bool vis[MAXN];
int N;//節點總個數,節點編號從0~N-1
void init(int n)
{
N = n;
tol = 0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int cap,int cost)
{
edge[tol].to = v;
edge[tol].cap = cap;
edge[tol].cost = cost;
edge[tol].flow = 0;
edge[tol].next = head[u];
head[u] = tol++;
edge[tol].to = u;
edge[tol].cap = 0;
edge[tol].cost = -cost;
edge[tol].flow = 0;
edge[tol].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);
while(!q.empty())
{
int u = q.front();
q.pop();
vis[u] = false;
for(int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if(edge[i].cap > edge[i].flow &&
dis[v] > dis[u] + edge[i].cost )
{
dis[v] = dis[u] + edge[i].cost;
pre[v] = i;
if(!vis[v])
{
vis[v] = true;
q.push(v);
}
}
}
}
if(pre[t] == -1)return false;
else return true;
}
//返回的是最大流,cost存的是最小費用
int minCostMaxflow(int s,int t,int &cost)
{
int flow = 0;
cost = 0;
while(spfa(s,t))
{
int Min = INF;
for(int i = pre[t]; 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[t]; i != -1; i = pre[edge[i^1].to])
{
edge[i].flow += Min;
edge[i^1].flow -= Min;
cost += edge[i].cost * Min;
}
flow += Min;
}
return flow;
}
int a[105];
int b[105];
int c[105][105];
int T;
int n,m,t;
int main()
{
freopen("job.in","r",stdin);
freopen("job.out","w",stdout);
// freopen("data.txt","r",stdin);
// scanf("%d", &T);
// while(T--)
// {
//
// }
int ss,tt;
scanf("%d", &n);
ss=0,tt=n+n+1;
init(500);
for(int i=1;i<=n;i++)
{
addedge(0,i,1,0);
}
for(int j=n+1;j<=n+n;j++)
{
addedge(j,tt,1,0);
// for(int i=0;i<t;i++)
// {
// scanf("%d",&w);
// addEdge(w,j,1);
// }
}
for(int i = 1 ;i <= n; i++)
{
for(int j = 1;j <= n; j++)
{
scanf("%d",&c[i][j]);
addedge(i,j+n,1,c[i][j]);
}
}
int cost =0 ;
minCostMaxflow(ss, tt,cost);
printf("%d\n",cost);
init(500);
for(int i=1;i<=n;i++)
{
addedge(0,i,1,0);
}
for(int j=n+1;j<=n+n;j++)
{
addedge(j,tt,1,0);
}
for(int i = 1 ;i <= n; i++)
{
for(int j = 1;j <= n; j++)
{
addedge(i,j+n,1,-c[i][j]);
}
}
minCostMaxflow(ss, tt,cost);
printf("%d\n",-cost);
// if(Maxflow(ss, tt)<sum)
// {
// printf("NoSolution!\n");
// }
// else
// {
//
// for(int i=1;i<=k;i++)
// {
// printf("%d:",i);
// for(int j = head[i]; j != -1; j = edge[j].next)
// {
// if(edge[j].flow==1)
// {
// printf("%d ",edge[j].to-k);
//
// }
// }
// printf("\n");
// }
//
//
// }
return 0;
}
最小代價供求
【問題分析】
轉化爲供求平衡問題,用最小費用最大流解決。
【建模方法】
首先求出所有倉庫存貨量平均值,設第i個倉庫的盈餘量爲A[i],A[i] = 第i個倉庫原有存貨量 - 平均存貨量。建立二分圖,把每個倉庫抽象爲兩個節點Xi和Yi。增設附加源S匯T。
1、如果A[i]>0,從S向Xi連一條容量爲A[i],費用爲0的有向邊。
2、如果A[i]<0,從Yi向T連一條容量爲-A[i],費用爲0的有向邊。
3、每個Xi向兩個相鄰頂點j,從Xi到Xj連接一條容量爲無窮大,費用爲1的有向邊,從Xi到Yj連接一條容量爲無窮大,費用爲1的有向邊。
求最小費用最大流,最小費用流值就是最少搬運量。
【建模分析】
計算出每個倉庫的盈餘後,可以把問題轉化爲供求問題。建立供求網絡,把二分圖X集合中所有節點看做供應節點,Y集合所有節點看做需求節點,在能一次搬運滿足供需的Xi和Yj之間連接一條費用爲1的有向邊,表示搬運一個單位貨物費用爲1。另外還要在Xi與相鄰的Xj之間連接邊,表示貨物可以暫時搬運過去,不立即滿足需求,費用也爲1。最大流滿足了所有的盈餘和虧損供求平衡,最小費用就是最少搬運量。
G 公司有n 個沿鐵路運輸線環形排列的倉庫,每個倉庫存儲的貨物數量不等。如何用最
少搬運量可以使n 個倉庫的庫存數量相同。搬運貨物時,只能在相鄰的倉庫之間搬運。
«編程任務:
對於給定的n 個環形排列的倉庫的庫存量,編程計算使n 個倉庫的庫存數量相同的最少
搬運量。
輸入描述 Input Description
第1 行中有1 個正整數n(n<=100),表示有n
個倉庫。第2 行中有n個正整數,表示n個倉庫的庫存量。
輸出描述 Output Description
將計算出的最少搬運量輸出
樣例輸入 Sample Input
5
17 9 14 16 4
樣例輸出 Sample Output
11
數據範圍及提示 Data Size & Hint
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 10000;
const int MAXM = 100000;
const int INF = 0x3f3f3f3f;
struct Edge
{
int to,next,cap,flow,cost;
} edge[MAXM];
int head[MAXN],tol;
int pre[MAXN],dis[MAXN];
bool vis[MAXN];
int N;//節點總個數,節點編號從0~N-1
void init(int n)
{
N = n;
tol = 0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int cap,int cost)
{
edge[tol].to = v;
edge[tol].cap = cap;
edge[tol].cost = cost;
edge[tol].flow = 0;
edge[tol].next = head[u];
head[u] = tol++;
edge[tol].to = u;
edge[tol].cap = 0;
edge[tol].cost = -cost;
edge[tol].flow = 0;
edge[tol].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);
while(!q.empty())
{
int u = q.front();
q.pop();
vis[u] = false;
for(int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if(edge[i].cap > edge[i].flow &&
dis[v] > dis[u] + edge[i].cost )
{
dis[v] = dis[u] + edge[i].cost;
pre[v] = i;
if(!vis[v])
{
vis[v] = true;
q.push(v);
}
}
}
}
if(pre[t] == -1)return false;
else return true;
}
//返回的是最大流,cost存的是最小費用
int minCostMaxflow(int s,int t,int &cost)
{
int flow = 0;
cost = 0;
while(spfa(s,t))
{
int Min = INF;
for(int i = pre[t]; 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[t]; i != -1; i = pre[edge[i^1].to])
{
edge[i].flow += Min;
edge[i^1].flow -= Min;
cost += edge[i].cost * Min;
}
flow += Min;
}
return flow;
}
int a[105];
int b[105];
int c[105][105];
int T;
int n,m,t;
int main()
{
freopen("overload.in","r",stdin);
freopen("overload.out","w",stdout);
// freopen("data.txt","r",stdin);
// scanf("%d", &T);
// while(T--)
// {
//
// }
int ss,tt;
scanf("%d",&n);
int sum =0;
ss=0,tt=n+1;
init(500);
for(int i=1;i<=n;i++)
{
scanf("%d",&t);
sum+=t;
addedge(0,i,t,0);
}
for(int i=1;i<=n;i++)
{
if(i!=1)
addedge(i,i-1,INF,1);
else
addedge(i,n,INF,1);
if(i!=n)
addedge(i,i+1,INF,1);
else
addedge(i,1,INF,1);
}
for(int j=1;j<=n;j++)
{
addedge(j,tt,sum/n,0);
}
int cost =0 ;
minCostMaxflow(ss,tt,cost);
printf("%d\n",cost);
return 0;
}
最大權不相交路徑
【問題分析】
最大權不相交路徑問題,可以用最大費用最大流解決。
【建模方法】
方法1
按左端點排序所有區間,把每個區間拆分看做兩個頂點
/*
* Problem: 線性規劃與網絡流24題 #21 最長k可重區間集問題
* Author: Guo Jiabao
* Time: 2009.6.30 12:32
* State: Solved
* Memo: 最大費用最大流
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
using namespace std;
const int MAXN=501*2,MAXM=(MAXN*MAXN/4+MAXN*3)*2,INF=~0U>>1;
struct Queue
{
int Q[MAXN],head,tail,size;
bool inq[MAXN];
void init()
{
memset(inq,0,sizeof(inq));
head = size =0; tail = -1;
}
void ins(int p)
{
size++;
if (++tail == MAXN) tail = 0;
Q[tail] = p;
inq[p]=true;
}
int pop()
{
size--;
int p=Q[head];
if (++head == MAXN) head = 0;
inq[p]=false;
return p;
}
}Q;
struct interval
{
int a,b;
}I[MAXN];
struct edge
{
edge *next,*op;
int t,c,v;
}*V[MAXN],ES[MAXM],*fe[MAXN],*P[MAXN];
int N,K,S,SS,T,EC,Ans,Costflow;
int dist[MAXN],ft[MAXN];
inline void addedge(int a,int b,int c,int v)
{
ES[++EC].next = V[a]; V[a]=ES+EC; V[a]->t=b; V[a]->c=c; V[a]->v=v;
ES[++EC].next = V[b]; V[b]=ES+EC; V[b]->t=a; V[b]->c=0; V[b]->v=-v;
V[a]->op = V[b]; V[b]->op = V[a];
//printf("(%d,%d) c=%d v=%d\n",a,b,c,v);
}
inline int cmp(const void *a,const void *b)
{
return ((interval *)a)->a - ((interval *)b)->a;
}
void init()
{
int i,j;
freopen("interv.in","r",stdin);
freopen("interv.out","w",stdout);
scanf("%d%d",&N,&K);
S=0; T=N+N+2; SS=T-1;
addedge(S,SS,K,0);
for (i=1;i<=N;i++)
{
scanf("%d%d",&I[i].a,&I[i].b);
addedge(SS,i,1,0);
addedge(i+N,T,1,0);
}
qsort(I+1,N,sizeof(I[0]),cmp);
for (i=1;i<=N;i++)
{
addedge(i,i+N,1,I[i].b-I[i].a);
P[i] = ES+EC-1;
for (j=i+1;j<=N;j++)
if (I[j].a>=I[i].b)
addedge(i+N,j,1,0);
}
}
bool SPFA()
{
int i,j;
for (i=S;i<=T;i++)
dist[i]=-INF;
dist[S]=0;
Q.ins(S);
while (Q.size)
{
i=Q.pop();
for (edge *e=V[i];e;e=e->next)
{
j=e->t;
if (e->c && dist[i] + e->v > dist[j])
{
dist[j] = dist[i] + e->v;
ft[j] = i;
fe[j] = e;
if (!Q.inq[j])
Q.ins(j);
}
}
}
return dist[T]!=-INF;
}
void Augment()
{
int i,delta=INF;
for (i=T;i!=S;i=ft[i])
if (fe[i]->c < delta)
delta = fe[i]->c;
for (i=T;i!=S;i=ft[i])
{
fe[i]->c -= delta;
fe[i]->op->c += delta;
Costflow += fe[i]->v * delta;
}
}
void SPFAFlow()
{
Q.init();
while (SPFA())
Augment();
}
int main()
{
init();
SPFAFlow();
printf("%d\n",Costflow);
/*for (int i=1;i<=N;i++)
if (P[i]->c==0)
printf("%d %d\n",I[i].a,I[i].b);*/
return 0;
}
/*
* Problem: 線性規劃與網絡流24題 #21 最長k可重區間集問題
* Author: Guo Jiabao
* Time: 2009.6.30 12:51
* State: Solved
* Memo: 最大費用最大流 區間離散化
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
using namespace std;
const int MAXN=501*2,MAXM=MAXN*2*2,INF=~0U>>1;
struct Queue
{
int Q[MAXN],head,tail,size;
bool inq[MAXN];
void init()
{
memset(inq,0,sizeof(inq));
head = size =0; tail = -1;
}
void ins(int p)
{
size++;
if (++tail == MAXN) tail = 0;
Q[tail] = p;
inq[p]=true;
}
int pop()
{
size--;
int p=Q[head];
if (++head == MAXN) head = 0;
inq[p]=false;
return p;
}
}Q;
struct interval
{
int a,b,len;
}I[MAXN];
struct edge
{
edge *next,*op;
int t,c,v;
}*V[MAXN],ES[MAXM],*fe[MAXN];
int N,K,C,S,T,EC,Ans,Costflow;
int dist[MAXN],ft[MAXN];
int *IA[MAXN];
inline void addedge(int a,int b,int c,int v)
{
ES[++EC].next = V[a]; V[a]=ES+EC; V[a]->t=b; V[a]->c=c; V[a]->v=v;
ES[++EC].next = V[b]; V[b]=ES+EC; V[b]->t=a; V[b]->c=0; V[b]->v=-v;
V[a]->op = V[b]; V[b]->op = V[a];
// printf("(%d,%d) c=%d v=%d\n",a,b,c,v);
}
inline int cmp(const void *a,const void *b)
{
return *(*(int **)a) - *(*(int **)b);
}
void init()
{
int i,last;
freopen("interv.in","r",stdin);
freopen("interv.out","w",stdout);
scanf("%d%d",&N,&K);
S=0; T=N+N+1;
for (i=1;i<=N;i++)
{
scanf("%d%d",&I[i].a,&I[i].b);
I[i].len = I[i].b - I[i].a;
IA[++C] = &I[i].a;
IA[++C] = &I[i].b;
}
qsort(IA+1,C,sizeof(IA[0]),cmp);
IA[0] = &S;
for (i=1,last=-INF;i<=C;i++)
{
if (*IA[i]!=last)
{
last = *IA[i];
*IA[i] = *IA[i-1] + 1;
}
else
*IA[i] = *IA[i-1];
}
addedge(S,1,K,0);
addedge(*IA[C],T,K,0);
for (i=1;i<C;i++)
addedge(i,i+1,INF,0);
for (i=1;i<=N;i++)
addedge(I[i].a,I[i].b,1,I[i].len);
}
bool SPFA()
{
int i,j;
for (i=S;i<=T;i++)
dist[i]=-INF;
dist[S]=0;
Q.ins(S);
while (Q.size)
{
i=Q.pop();
for (edge *e=V[i];e;e=e->next)
{
j=e->t;
if (e->c && dist[i] + e->v > dist[j])
{
dist[j] = dist[i] + e->v;
ft[j] = i;
fe[j] = e;
if (!Q.inq[j])
Q.ins(j);
}
}
}
return dist[T]!=-INF;
}
void Augment()
{
int i,delta=INF;
for (i=T;i!=S;i=ft[i])
if (fe[i]->c < delta)
delta = fe[i]->c;
for (i=T;i!=S;i=ft[i])
{
fe[i]->c -= delta;
fe[i]->op->c += delta;
Costflow += fe[i]->v * delta;
}
}
void SPFAFlow()
{
Q.init();
while (SPFA())
Augment();
}
int main()
{
init();
SPFAFlow();
printf("%d\n",Costflow);
/*for (int i=1;i<=N;i++)
if (P[i]->c==0)
printf("%d %d\n",I[i].a,I[i].b);*/
return 0;
}
二分圖最大獨立集
【問題分析】
二分圖最大獨立集,轉化爲二分圖最大匹配,從而用最大流解決。
【建模方法】
首先把棋盤黑白染色,使相鄰格子顏色不同。把所有可用的黑色格子看做二分圖X集合中頂點,可用的白色格子看做Y集合頂點。建立附加源S匯T,從S向X集合中每個頂點連接一條容量爲1的有向邊,從Y集合中每個頂點向T連接一條容量爲1的有向邊。從每個可用的黑色格子向騎士一步能攻擊到的可用的白色格子連接一條容量爲無窮大的有向邊。求出網絡最大流,要求的結果就是可用格子的數量減去最大流量。
【建模分析】
用網絡流的方法解決棋盤上的問題,一般都要對棋盤黑白染色,使之成爲一個二分圖。放儘可能多的不能互相攻擊的騎士,就是一個二分圖最大獨立集問題。有關二分圖最大獨立集問題,更多討論見《最小割模型在信息學競賽中的應用》作者胡伯濤。
該題規模比較大,需要用效率較高的網絡最大流算法解決。
在一個n*n個方格的國際象棋棋盤上,馬(騎士)可以攻擊的棋盤方格如圖所示。棋盤
上某些方格設置了障礙,騎士不得進入。
對於給定的n*n個方格的國際象棋棋盤和障礙標誌,計算棋盤上最多可以放置多少個騎
士,使得它們彼此互不攻擊。
輸入描述 Input Description
第一行有2 個正整數n 和m (1<=n<=200, 0<=m
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 41000;
const int MAXM = 10000010;
const int INF = 0x3f3f3f3f;
//const ll MOD = 998244353;
int dir[8][2]={ {2,1},{-2,1},{2,-1},{-2,-1} , {1,2},{1,-2},{-1,2},{-1,-2} };
inline int read(){int ra,fh;char rx;rx=getchar(),ra=0,fh=1;
while((rx<'0'||rx>'9')&&rx!='-')rx=getchar();if(rx=='-')
fh=-1,rx=getchar();while(rx>='0'&&rx<='9')ra*=10,ra+=rx-48,
rx=getchar();return ra*fh;}
struct Edge
{
int from, to, cap, flow, next;
};
Edge edge[MAXM];
int head[MAXN], cur[MAXN], edgenum;
int dist[MAXN];
bool vis[MAXN];
int N, M,ss,tt;
void init()
{
edgenum = 0;
memset(head, -1, sizeof(head));
}
void addEdge(int u, int v, int w)
{
Edge E1 = {u, v, w, 0, head[u]};
edge[edgenum] = E1;
head[u] = edgenum++;
Edge E2 = {v, u, 0, 0, head[v]};
edge[edgenum] = E2;
head[v] = edgenum++;
}
bool BFS(int s, int t)
{
queue<int> Q;
memset(dist, -1, sizeof(dist));
memset(vis, false, sizeof(vis));
dist[s] = 0;
vis[s] = true;
Q.push(s);
while(!Q.empty())
{
int u = Q.front();
Q.pop();
for(int i = head[u]; i != -1; i = edge[i].next)
{
Edge E = edge[i];
if(!vis[E.to] && E.cap > E.flow)
{
dist[E.to] = dist[u] + 1;
if(E.to == t) return true;
vis[E.to] = true;
Q.push(E.to);
}
}
}
return false;
}
int DFS(int x, int a, int t)
{
if(x == t || a == 0) return a;
int flow = 0, f;
for(int &i = cur[x]; i != -1; i = edge[i].next)
{
Edge &E = edge[i];
if(dist[E.to] == dist[x] + 1 && (f = DFS(E.to, min(a, E.cap - E.flow), t)) > 0)
{
edge[i].flow += f;
edge[i^1].flow -= f;
flow += f;
a -= f;
if(a == 0) break;
}
}
return flow;
}
int Maxflow(int s, int t)
{
int flow = 0;
while(BFS(s, t))
{
memcpy(cur, head, sizeof(head));
flow += DFS(s, INF, t);
}
return flow;
}
int mm[205][205];
int m,n;
int u,v;
int getnum(int x,int y)
{
return (x-1)*n+y;
}
void dfs(int curx,int cury,int now)
{
mm[curx][cury] = now;
for(int i=0 ;i<8;i++)
{
int xx = curx + dir[i][0];
int yy = cury + dir[i][1];
if(xx<1||xx>n||yy<1||yy>n||mm[xx][yy])
{
continue;
}
if(now==2)
{
// addEdge(getnum(curx,cury),getnum(xx,yy),INF);
dfs(xx,yy,3);
}
else
{
// addEdge(getnum(xx,yy),getnum(curx,cury),INF);
dfs(xx,yy,2);
}
}
}
int main()
{
// freopen("data.txt","r",stdin);
ios_base::sync_with_stdio(false);
cin >> n>>m;
init();
int s = 0;
int t = n*n+1;
for(int i=0;i<m;i++)
{
cin >> u>>v;
mm[u][v]=1;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(mm[i][j]==0)
{
dfs(i,j,2);
}
}
}
// for(int i=1;i<=n;i++)
// {
// for(int j=1;j<=n;j++)
// {
// cout << mm[i][j]<<" ";
// }
// cout <<endl;
// }
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(mm[i][j]==2)
{
addEdge(s,getnum(i,j),1);
for(int d=0 ;d<8;d++)
{
int xx = i + dir[d][0];
int yy = j + dir[d][1];
if(xx<1||xx>n||yy<1||yy>n)
{
continue;
}
if(mm[xx][yy]==3)
{
addEdge(getnum(i,j),getnum(xx,yy),INF);
}
}
}
if(mm[i][j]==3)
{
addEdge(getnum(i,j),t,1);
}
}
}
cout << n*n-m-Maxflow(s,t)<<endl;
return 0;
}