题意:
有N个人,M个星球,有一个N*M的矩阵,表示某个人是否可以去某个星球, 每个星球有最大容纳人数。问这些人能不能全安排到这些星球上?
思路:
- 二分图的多重匹配
- 最大流。因为n很大,所以直接建图会内存超限,考虑M的上限很小(10),对所有人而言,去星球的状态最多2^M = 1<<M种,将相同的状态合并。
多重匹配
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <algorithm>
typedef long long LL;
const int INF = 0x3f3f3f3f;
const int maxn = 100000 + 500;
const int maxm = 12;
using namespace std;
int n,m;
int G[maxn][maxm];
int nx,ny; //nx表示二分图左边顶点的个数,ny表示二分图右边顶点的个数
int link[maxm][maxn], num[maxm];
bool vis[maxm]; // 只对右边(Y集合里)的点标记,防止死循环。
// 寻找左边(X集合里)u点到右边的增广路,如果有返回,没有返回0 。从左往右是找非匹配边,而从右往左找匹配边。
bool GetAugumentPath(int u){
for(int v = 0; v < ny; ++v){
if(G[u][v]&&!vis[v]){ // 右边的点没有访问过
vis[v] = 1;
if(link[v][0] < num[v]){ // 还没满
link[v][++link[v][0]] = u;
return true;
}
for(int i = 1; i <= num[v]; ++i) if(GetAugumentPath(link[v][i])){
link[v][i] = u;
return true;
}
}
}
return false;
}
int solve(){
int ans = 0;//对二分图左边的所有顶点进行遍历
for(int i = 0; i < ny; ++i) link[i][0] = 0;
for(int i = 0; i < nx; ++i){
memset(vis, 0, sizeof(vis));
if(GetAugumentPath(i)) ++ans;
else return -1;
}
return ans;
}
int main()
{
//freopen("in.txt","r",stdin);
while(scanf("%d%d",&n,&m) == 2){
nx = n; ny = m;
for(int i = 0; i < n; ++i)
for(int j = 0; j < m; ++j)
scanf("%d",&G[i][j]);
for(int i = 0; i < m; ++i) scanf("%d",&num[i]);
int ans = solve();
if(ans != -1) printf("YES\n");
else printf("NO\n");
}
fclose(stdin);
return 0;
}
最大流
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <algorithm>
typedef long long LL;
const int INF = 0x3f3f3f3f;
const int maxn = 1200 + 50;
using namespace std;
int n,m;
// 图
struct Edge{
int u, v, cap, flow;
Edge(int a, int b, int c, int d):u(a),v(b),cap(c),flow(d){}
};
vector<Edge> edges;
vector<int> G[maxn];
int dis[maxn]; // 分层的编号
int cur[maxn]; // 当前弧,重要优化!!!
void init(int a){
for(int i = 0; i < a; ++i) G[i].clear();
edges.clear();
}
void addEdge(int u, int v, int cap){
edges.push_back(Edge(u,v,cap,0));
edges.push_back(Edge(v,u,0,0)); // 反向弧
int m = edges.size();
G[u].push_back(m-2);
G[v].push_back(m-1);
}
// 分层
bool bfs(int s, int t){
memset(dis, -1, sizeof(dis));
dis[s] = 0;
queue<int> Q;
Q.push(s);
while(!Q.empty()){
int x = Q.front(); Q.pop();
for(int i = 0; i < G[x].size(); ++i){
Edge e = edges[G[x][i]];
if(dis[e.v] == -1&&e.cap > e.flow){
dis[e.v] = dis[x] + 1;
Q.push(e.v);
}
}
}
return dis[t] != -1;
}
int dfs(int s, int t, int cur_flow){
if(s == t||cur_flow == 0) return cur_flow;
int ans = 0;
for(int& i = cur[s]; i < G[s].size(); ++i){
int c = G[s][i];
Edge e = edges[c];
if(dis[e.v] == dis[s] + 1&&e.cap > e.flow){
int a2 = min(cur_flow, e.cap-e.flow);
int w = dfs(e.v, t, a2);
edges[c].flow += w;
edges[c^1].flow -= w;
cur_flow -= w;
ans += w;
if(cur_flow <= 0) break;
}
}
return ans;
}
// 最大流
int Dinic(int s, int t){
int ans = 0;
while(bfs(s,t)){
memset(cur, 0, sizeof(cur));
ans += dfs(s,t,INF);
}
return ans;
}
int cnt1[maxn];
void state_cut(int s, int t){
memset(cnt1, 0, sizeof(cnt1));
for(int i = 0; i < n; ++i){
int tmp = 0;
for(int j = 0; j < m; ++j){
int a; scanf("%d",&a);
if(a == 1) tmp += (1<<j);
}
++cnt1[tmp]; //给这个状态计数
}
for(int i = 0; i < (1<<m); ++i) if(cnt1[i]){
addEdge(s, i, cnt1[i]);
for(int j = 0; j < m; ++j) if(i&(1<<j)) addEdge(i, t+1+j, INF);
}
for(int i = 0; i < m; ++i){
int a; scanf("%d",&a);
addEdge(t+1+i, t, a);
}
}
int main()
{
//freopen("in.txt","r",stdin);
while(scanf("%d%d",&n,&m) == 2){
init(maxn);
int s = 1024, t = s+1;
state_cut(s, t);
int ans = Dinic(s, t);
if(ans == n) printf("YES\n");
else printf("NO\n");
}
fclose(stdin);
return 0;
}