說到二分圖的多重最大匹配問題,就可以將其看成由超級原點到超級匯點間的最大流問題。而二分圖已經天然將超級源點和超級匯點間的結點劃分了不同的層次,因此利用dinic就會跑的飛快。當然,根據這個原理,上述dinic算法模板還可以在層次圖的計算上進行化簡。
Time Limit:10000ms
Case Time Limit:1000ms
Memory Limit:256MB
描述
學校的秋季運動會即將開始,爲了決定參賽人員,各個班又開始忙碌起來。
小Hi和小Ho作爲班上的班幹部,統計分配比賽選手的重任也自然交到了他們手上。
已知小Hi和小Ho所在的班級一共有N名學生(包含小Hi和小Ho),編號依次爲1..N。
運動會一共有M項不同的比賽,編號爲1..M。第i項比賽每個班需要派出m[i]名選手參加。
根據小Hi和小Ho的統計,編號爲i的學生表示最多同時參加a[i]項比賽,並且給出他所擅長的b[i]項比賽的編號。
小Hi和小Ho希望將每個學生都安排到他所擅長的比賽項目,以增加奪冠的可能性。同時又要考慮滿足每項比賽對人數的要求,當然給一個學生安排的比賽項目也不能超過他願意參加的比賽項目數量。
根據統計的結果,小Hi和小Ho想知道能否有一個合適的安排,同時滿足這些條件。
提示:二分圖多重匹配
輸入
第1行:1個整數T,表示一共有T(2≤T≤5)組數據,每組數據按如下格式給出:
第1行:2個正整數N,M。1≤N≤100,1≤M≤100。
第2行:M個整數,第i個數表示第i個項目需要的選手數量m[i]。1≤m[i]≤N。
第3..N+2行:若干整數,第i+2行表示編號爲i的學生的信息。先是a[i],b[i],接下來b[i]個整數,表示其所擅長的b[i]個項目。1≤a[i]≤M
輸出
第1..T行:第i行表示第i組數據能否滿足要求,若能夠輸出”Yes”,否則輸出”No”。
Sample Input
2
4 3
1 2 2
1 2 1 2
2 2 1 3
1 1 2
1 2 2 3
4 3
2 2 2
1 2 1 2
2 2 1 3
1 1 2
1 2 2 3
Sample Output
Yes
No
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
const int MAXN = 210;
const int MAXM = 210*210;
const int INF = 0x3f3f3f3f;
struct Edge
{
int v, f;
int next;
}edge[MAXM];
int cnt;
int head[MAXN], level[MAXN];
int q[MAXN];
int a[110],b[110],m[110];
vector<int > v[110];
void init()
{
cnt = 0;
memset(head, -1, sizeof(head));
}
void read_graph(int u, int v, int f)
{
edge[cnt].v = v, edge[cnt].f = f;
edge[cnt].next = head[u], head[u] = cnt++;
edge[cnt].v = u, edge[cnt].f = 0; //增加一條反向弧,容量爲0
edge[cnt].next = head[v], head[v] = cnt++;
}
int bfs(int s, int t) //構建層次網絡
{
memset(level, 0, sizeof(level));
level[s] = 1;
int front = 0, rear = 1;
q[front] = s;
while(front < rear)
{
int x = q[front++];
if(x == t) return 1;
for(int e = head[x]; e != -1; e = edge[e].next)
{
int v = edge[e].v, f = edge[e].f;
if(!level[v] && f)
{
level[v] = level[x] + 1;
q[rear++] = v;
}
}
}
return 0;
}
int dfs(int u, int maxf, int t)
{
if(u == t) return maxf;
int ret = 0;
for(int e = head[u]; e != -1; e = edge[e].next)
{
int v = edge[e].v, f = edge[e].f;
if(level[u] + 1 == level[v] && f)
{
int Min = min(maxf-ret, f);
f = dfs(v, Min, t);
edge[e].f -= f;
edge[e^1].f += f;
ret += f;
if(ret == maxf) return ret;
}
}
return ret;
}
int Dinic(int s, int t) //Dinic
{
int ans = 0;
while(bfs(s, t)) ans += dfs(s, INF, t);
return ans;
}
int main()
{
int T;
cin>>T;
while(T--)
{
init();
int N,M;
cin>>N>>M;
int sum =0;
for(int i = 0 ;i<M;i++)
{
cin>>m[i];
sum +=m[i];
read_graph(N+1+i,N+M+1,m[i]);
}
for(int i =0 ;i<N;i++)
{
cin>>a[i];
read_graph(0,i+1,a[i]);
cin>>b[i];
int x;
for(int j =0 ;j< b[i];j++)
{
cin>>x;
read_graph(i+1,x+N,1);
}
}
if(Dinic(0,N+M+1)==sum)
puts("Yes");
else puts("No");
}
return 0;
}
建立一個超級源點,一個超級匯點,將所有的運動員與源點間連接一條容量爲a[i]的邊,再將所有的運動員和其所能對應的項目間連接一條容量爲1的邊。最後將所有的項目和超級匯點間建立容量爲m[i]的邊即可。只需判斷最後的比賽項目和超級匯點的邊是否滿流即可。
二分圖多重最大匹配,是原有二分圖上可以匹配的點都可以和多條匹配邊相關聯,但每個點都有自己的匹配上限。
解決方法一是利用網絡流,就像上面那樣;
解決方法二是改進匈牙利算法,需要結合二分搜索。
下面來看一道題
HDU3605 Escape
題目中有1e6個點,每個店的決策有10種,如果建圖的話一定會TLE,這時候就可以想到狀壓的方法,將相同點進行合併,這樣就可以將1e6*10的複雜度降到1024*10,大大縮短了運行時間。
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <ctype.h>
#include <queue>
#include <map>
#include <algorithm>
using namespace std;
const int INF=1e9;
int head[2000], s, t, nv, n, cnt;
int num[2000], d[2000], pre[2000], cur[2000], q[2000], fei[2000];
struct node
{
int u, v, next, cap;
}edge[1000000];
void add(int u, int v, int cap)
{
edge[cnt].v=v;
edge[cnt].cap=cap;
edge[cnt].next=head[u];
head[u]=cnt++;
edge[cnt].v=u;
edge[cnt].cap=0;
edge[cnt].next=head[v];
head[v]=cnt++;
}
void bfs()
{
memset(num,0,sizeof(num));
memset(d,-1,sizeof(d));
int f1=0, f2=0, i;
q[f1++]=t;
d[t]=0;
num[0]=1;
while(f1>=f2)
{
int u=q[f2++];
for(i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(d[v]==-1)
{
d[v]=d[u]+1;
num[d[v]]++;
q[f1++]=v;
}
}
}
}
int isap()
{
memcpy(cur,head,sizeof(cur));
int flow=0, i, u=pre[s]=s;
bfs();
while(d[s]<nv)
{
if(u==t)
{
int f=INF, pos;
for(i=s;i!=t;i=edge[cur[i]].v)
{
if(f>edge[cur[i]].cap)
{
f=edge[cur[i]].cap;
pos=i;
}
}
for(i=s;i!=t;i=edge[cur[i]].v)
{
edge[cur[i]].cap-=f;
edge[cur[i]^1].cap+=f;
}
flow+=f;
if(flow>=n)
return flow;
u=pos;
}
for(i=cur[u];i!=-1;i=edge[i].next)
{
if(d[edge[i].v]+1==d[u]&&edge[i].cap)
{
break;
}
}
if(i!=-1)
{
cur[u]=i;
pre[edge[i].v]=u;
u=edge[i].v;
}
else
{
if(--num[d[u]]==0) break;
int mind=nv;
for(i=head[u];i!=-1;i=edge[i].next)
{
if(mind>d[edge[i].v]&&edge[i].cap)
{
mind=d[edge[i].v];
cur[u]=i;
}
}
d[u]=mind+1;
num[d[u]]++;
u=pre[u];
}
}
return flow;
}
int main()
{
int m, x, i, j, top, y, z, num, a[20];
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(head,-1,sizeof(head));
memset(fei,0,sizeof(fei));
cnt=0;
s=0;
top=0;
num=0;
for(i=1;i<=n;i++)
{
x=0;
for(j=1;j<=m;j++)
{
scanf("%d",&y);
x=x*2+y;
}
fei[x]++;
}
for(i=1;i<=1100;i++)
{
if(fei[i])
{
num++;
}
}
t=num+m+1;
nv=t+1;
for(i=1;i<=1100;i++)
{
if(fei[i])
{
//printf("--%d %d\n", i, fei[i]);
top++;
add(s,top,fei[i]);
x=i;
z=m+1;
while(x)
{
y=x%2;
z--;
if(y)
{
add(top,z+num,INF);
}
//printf("--%d %d %d %d--",y, top, z, num);
x=x/2;
}
//printf("\n");
}
}
for(i=1;i<=m;i++)
{
scanf("%d",&x);
add(i+num,t,x);
}
x=isap();
if(x>=n)
printf("YES\n");
else
printf("NO\n");
}
return 0;
}