HDU - 3605 Escape (缩点+最大流/二分图多重匹配)
题意:有N(1<=N<=1e5)个人要移民到M(1<=M<=10)个星球上,每个人有自己想去的星球,每个星球有最大承载人数。问这N个人能否移民成功。
分析:可以用最大流的思路求解该问题,新建源点和汇点,源点与人间加入弧,流量为他想去的星球之和;星球和汇点间加入弧,流量为其承载数量;人和星球间加入弧,流量无限。但是本题中N很大,M却很小,所以想去星球的编号组成的二进制数最多不超过1024,那么可以将N个人缩点。然后跑最大流,满流为N。
#include<iostream> #include<stdio.h> #include<cmath> #include<cstring> #include<algorithm> #include<vector> #include<queue> using namespace std; const int MAXN =1e4+5,maxm =1e6+5; const int INF=0x3f3f3f3f; struct ISAP{ int head[MAXN], nv, n, tot; //nv:编号修改的上限 int num[MAXN], d[MAXN], pre[MAXN], cur[MAXN], q[MAXN]; struct node{ int v, next, cap; }edge[maxm]; void init(){ memset(head,-1,sizeof(head)); tot=0; } void AddEdge(int u, int v, int cap){ edge[tot].v=v;edge[tot].cap=cap;edge[tot].next=head[u]; head[u]=tot++; edge[tot].v=u;edge[tot].cap=0;edge[tot].next=head[v]; head[v]=tot++; } void bfs(int s,int t){ 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(int s,int t){ memcpy(cur,head,sizeof(cur)); int flow=0, i, u=pre[s]=s; bfs(s,t); 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; } }F; int vis[1200]; int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif int S,T,N,M,u,v,tmp,k; int op; while(scanf("%d%d",&N,&M)==2){ F.init(); k=0; memset(vis,0,sizeof(vis)); for(int i=1;i<=N;++i){ int x=0; for(int j=1;j<=M;++j){ scanf("%d",&op); x<<=1; x+=op; //转换为二进制状态 } vis[x]++; } for(int i=1;i<=1050;++i) if(vis[i]) k++; //记录缩点后点数 S=0;T=k+M+1; F.nv=k+M+2,F.n = N; int id=0; for(int i=1;i<=1050;++i){ if(!vis[i]) continue; id++; int sta = i,cnt = vis[i]; for(int j=1,t=1;j<=sta;j<<=1,t++){ if(j&sta) F.AddEdge(id,t+k,INF); } F.AddEdge(S,id,cnt); } for(int i=1;i<=M;++i){ scanf("%d",&tmp); F.AddEdge(i+k,T,tmp); } if(F.isap(S,T)==N) printf("YES\n"); else printf("NO\n"); } return 0; }
还有一种做法是二分图多重匹配。二分图多重匹配的思路与匈牙利算法接近,也是从以匹配的点中寻找增广路。X部为N个人,Y部为M个星球,每个星球有自己的匹配上限。在时间上,两种实现方法所差不多。
#include<iostream> #include<stdio.h> #include<cmath> #include<cstring> #include<algorithm> #include<vector> using namespace std; typedef long long LL; const int maxn = 1e5+5,maxm = 2e6+5; int N,M; struct Node{ int K[15]; }link[maxn]; int cnt[15]; struct Edge{ int to,next; }edges[maxm]; int head[maxn],tot; bool used[15]; int limit[15]; void init() { tot=0; memset(head,-1,sizeof(head)); } void AddEdge(int u,int v) { edges[tot].to = v; edges[tot].next = head[u]; head[u] = tot++; } bool dfs(int u){ int v; for(int i=head[u];~i;i = edges[i].next){ v = edges[i].to; if(!used[v]){ used[v]=true; if(cnt[v]<limit[v]){ link[v].K[cnt[v]++]=u; return true; } for(int j=0;j<cnt[v];++j){ if(dfs(link[v].K[j])){ link[v].K[j]=u; return true; } } } } return false; } bool hungary(){ memset(cnt,0,sizeof(cnt)); for(int u=1;u<=N;u++){ memset(used,0,sizeof(used)); if(!dfs(u)) return false; //只要有一个人不能匹配则失败 } return true; } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif int T,u,v,tmp,k; int op; while(scanf("%d%d",&N,&M)==2){ init(); for(int i=1;i<=N;++i){ for(int j=1;j<=M;++j){ scanf("%d",&op); if(op) AddEdge(i,j); } } for(int i=1;i<=M;++i) scanf("%d",&limit[i]); if(hungary()) printf("YES\n"); else printf("NO\n"); } return 0; }
为了更好的明天