说到二分图的多重最大匹配问题,就可以将其看成由超级原点到超级汇点间的最大流问题。而二分图已经天然将超级源点和超级汇点间的结点划分了不同的层次,因此利用dinic就会跑的飞快。当然,根据这个原理,上述dinic算法模板还可以在层次图的计算上进行化简。
hihoCoder1393
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
#include
#include
#include
#include
#include
#include
#include
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 ;icin>>m[i];
sum +=m[i];
read_graph(N+1+i,N+M+1,m[i]);
}
for(int i =0 ;icin>>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
#include
#include
#include
#include
#include
#include
#include
#include
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]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;
}