二分图匹配题集
ZOJ 3988 Prime Set
这个题还是很怪,在建图是,“0元素”和“1元素”不明显,而在产生了最大匹配后,才感觉像是二分图。
首先素数筛得到素数后,判断符合条件的PrIme Set集合。
然后强行跑二分图最大匹配,得到最大匹配数$cnt$,同时得到集合中未匹配元素$num$。
如果$cnt≥k$,那我只要从最大匹配中取$k$对就可以了,答案$×2$,因为最大匹配两端本来就两两互不相交。
否则$cnt<k$, 我最后答案就是$cnt\times 2 + min(num, k - cnt)$,未匹配的元素之所以未匹配,就是因为之前和它相连的点,已经被进入到最大匹配中了,导致它失配,所以剩下的元素只能一个一个取了。
#include <algorithm> #include <cstring> #include <vector> #include <cstdio> using namespace std; const int maxn = 3e3 + 5; vector<int> g[maxn]; int vis[maxn], match[maxn]; int dfs(int x) { vis[x] = 1; for(int i = 0; i < g[x].size(); i++) { int y = g[x][i]; if(!vis[y]) { vis[y] = 1; if(!match[y] || dfs(match[y])) { match[y] = x; match[x] = y; return true; } } } return false; } void add(int u, int v) { g[u].push_back(v); g[v].push_back(u); } //线性素数筛 const int N = 2e6 + 50; int prime[N], num_prime = 0; int isNotPrime[N]; void is_prime(int N) { isNotPrime[0] = isNotPrime[1] = 1; for(int i=2;i<N;i++) { if(!isNotPrime[i]) { prime[num_prime++] = i; } for(int j=0;j<num_prime&&i*prime[j]<N;j++) { isNotPrime[i*prime[j]] = 1; if(!(i%prime[j])) { break; } } } return; } int a[maxn]; int main() { is_prime(2e6 + 5); int T; scanf("%d", &T); while(T--) { int n, k; scanf("%d %d", &n, &k); for(int i = 1; i <= n; i++) { match[i] = -1; scanf("%d", &a[i]); g[i].clear(); } for(int i = 1; i <= n; i++) { for(int j = i + 1; j <= n; j++) { if(!isNotPrime[a[i] + a[j]]) { add(i, j); match[i] = match[j] = 0; } } } int cnt = 0; for(int i = 1; i <= n; i++) { if(!match[i]) { memset(vis, 0, sizeof(vis)); if(dfs(i)) { cnt++; } } } int num = 0; for(int i = 1; i <= n; i++) { if(!match[i]) { num++; } } if(cnt >= k) printf("%d\n", k * 2); else printf("%d\n", cnt * 2 + min(num, k - cnt)); } return 0; }
ZOJ 1564 Place the Robots
此题和POJ 2226 Muddy Fields很像。
#include <algorithm> #include <cstring> #include <vector> #include <cstdio> using namespace std; const int maxn = 2480 + 5; vector<int> g[maxn]; int vis[maxn], match[maxn]; int dfs(int x) { vis[x] = 1; for(int i = 0; i < g[x].size(); i++) { int y = g[x][i]; if(!vis[y]) { vis[y] = 1; if(!match[y] || dfs(match[y])) { match[y] = x; match[x] = y; return true; } } } return false; } void add(int u, int v) { g[u].push_back(v); g[v].push_back(u); } char s[55][55]; int l[55][55], c[55][55]; int main() { int T, kase = 0; scanf("%d", &T); while(T--) { int n, m; scanf("%d %d", &n, &m); for(int i = 1; i <= n; i++) { scanf("%s", s[i] + 1); } int num = 0; memset(l, 0, sizeof(l)); memset(c, 0, sizeof(c)); for(int i = 1; i <= n; i++) { for(int j = 1; j <= m; j++) { if(s[i][j] == 'o' || s[i][j] == '*') { if(!l[i][j - 1]) { l[i][j] = ++num; } else l[i][j] = l[i][j - 1]; if(!c[i - 1][j]) { c[i][j] = ++num; } else c[i][j] = c[i - 1][j]; } } } for(int i = 1; i <= num; i++) { match[i] = 0; g[i].clear(); } for(int i = 1; i <= n; i++) { for(int j = 1; j <= m; j++) { if(s[i][j] == 'o') { add(l[i][j], c[i][j]); } } } int ans = 0; for(int i = 1; i <= num; i++) { if(!match[i]) { memset(vis, 0, sizeof(vis)); if(dfs(i)) { ans++; } } } printf("Case :%d\n%d\n", ++kase, ans); } return 0; }
POJ 1112 Team Them Up!
要求每个团队内的所有人互相认识,不考虑这一点还好说。
建补图的题也做过几个了,还不是特别敏感,这个题也是建补图POJ 2942。本题转换为补图后,那有边相连的两个点肯定在不同的团队中。
然后对每一个连通分量,跑二分图判定,把一个联通分量中的点分成两部分,设为$cnt[i][0],cnt[i][1]$,分别表示第$i$个连通分量中对等的两部分。
将$n$个物品分成两堆,使差值最小,是经典的$DP$问题。
$dp[i][j]=(dp[i-1][j-cnt[i][0] | dp[i-1][j-cnt[i][1]])$. $dp[i][j]$表示前$i$个连通分量中,每个分量取其中一个,能够构成的状态。顺着最后一个状态往前推打印路径。
不能用滚动数组,中间状态用来转移。
#include <algorithm> #include <cstring> #include <vector> #include <cstdio> using namespace std; int mp[111][111]; int g[111][111]; int cnt[111][3]; int flag = 0; int vis[111]; vector<int> belong[111][3]; int n; void dfs(int idx, int u, int color) { vis[u] = color; cnt[idx][color]++; belong[idx][color].push_back(u); for(int i = 1; i <= n; i++) { if(i == u) continue; int v = i; if(g[u][v]) { if(vis[v] == 0) { dfs(idx, v, 3 - color); } else if(vis[v] == color) { flag = 1; return; } } } } int dp[111][111]; void Print(int idx, int sum) { if(idx <= 0) return; if(sum >= cnt[idx][1] && dp[idx - 1][sum - cnt[idx][1]]) { for(int i = 0; i < (int)belong[idx][1].size(); i++) { int v = belong[idx][1][i]; vis[v] = 1; } Print(idx - 1, sum - cnt[idx][1]); } else if(sum >= cnt[idx][2] && dp[idx - 1][sum - cnt[idx][2]]) { for(int i = 0; i < (int)belong[idx][2].size(); i++) { int v = belong[idx][2][i]; vis[v] = 1; } Print(idx - 1, sum - cnt[idx][2]); } } int main() { scanf("%d", &n); for(int i = 1; i <= n; i++) { int tmp; scanf("%d", &tmp); while(tmp) { g[i][tmp] = 1; scanf("%d", &tmp); } } for(int i = 1; i <= n; i++) { for(int j = 1; j <= n; j++) { if(g[i][j] == 0 || g[j][i] == 0) { g[i][j] = g[j][i] = 0; } } } for(int i = 1; i <= n; i++) { for(int j = i + 1; j <= n; j++) { if(g[i][j] == 1) { g[i][j] = g[j][i] = 0; } else { g[i][j] = g[j][i] = 1; } } } int num = 0; for(int i = 1; i <= n; i++) { if(!vis[i]) { num++; dfs(num, i, 1); } } if(flag) { printf("No solution\n"); return 0; } dp[0][0] = 1; for(int i = 1; i <= num; i++) { for(int j = 0; j <= n / 2; j++) { if(j >= cnt[i][1] && dp[i - 1][j - cnt[i][1]]) dp[i][j] = 1; if(j >= cnt[i][2] && dp[i - 1][j - cnt[i][2]]) dp[i][j] = 1; } } memset(vis, 0, sizeof(vis)); int pre = 0; for(int i = n / 2; i >= 0; i--) { if(dp[num][i]) { Print(num, i); pre = i; break; } } printf("%d ", pre); for(int i = 1; i <= n; i++) { if(vis[i]) { printf("%d ", i); } } printf("\n"); printf("%d ", n - pre); for(int i = 1; i <= n; i++) { if(!vis[i]) { printf("%d ", i); } } return 0; } /* 2 1 2 0 2 1 0 */
POJ 3189 Steady Cow Assignment
二分图的多重匹配问题。
d哥也说过,网络流解决多重匹配最高效,图很好建,但没注意到题目中B的范围,枚举最大排名和最小排名跑最大流就可以了。
#include <algorithm> #include <cstring> #include <vector> #include <cstdio> #include <queue> using namespace std; #define next Next const int inf = 0x3f3f3f3f; const int maxn=1200; int level[maxn]; int iter[maxn]; int head[maxn],tot; struct edge{ int to,cap,Next; } e[45000]; //此处应为边的两倍,加一条容量为0的反向边 void init(int n){ for(int i = 1; i <= n; i++) head[i] = -1; tot=0; } void add(int from,int to,int cap){ e[tot].Next=head[from]; e[tot].to=to; e[tot].cap=cap; head[from]=tot; tot++; } void addedge(int from,int to,int cap){ add(from,to,cap); add(to,from,0); } void bfs(int s){ memset(level,-1,sizeof(level)); queue<int> q; level[s]=0; q.push(s); while(!q.empty()){ int v=q.front(); q.pop(); for(int i=head[v];~i;i=e[i].Next){ edge &ed=e[i]; if(ed.cap>0&&level[ed.to]<0){ level[ed.to]=level[v]+1; q.push(ed.to); } } } } int dfs(int v,int t,int f){ if(v==t) return f; for(int &i=iter[v];~i;i=e[i].Next){ edge &ed=e[i]; if(ed.cap>0&&level[v]<level[ed.to]){ int d=dfs(ed.to,t,min(f,ed.cap)); if(d>0){ ed.cap-=d; e[i^1].cap+=d; return d; } } } return 0; } int max_flow(int s,int t){ int flow=0; while(1){ bfs(s); if(level[t]<0) return flow; memcpy(iter,head,sizeof(iter)); int f; while((f=dfs(s,t,inf))>0){ flow+=f; } } } vector<int> g[1111]; int c[22]; int main() { int n, b; scanf("%d %d", &n, &b); for(int i = 1; i <= n; i++) { for(int j = 1; j <= b; j++) { int x; scanf("%d", &x); g[i].push_back(x); } } for(int i = 1; i <= b; i++) scanf("%d", &c[i]); int diff = 100; for(int i = 1; i <= b; i++) { for(int j = i; j <= b; j++) { init(n + b + 2); int s = n + b + 1; int t = n + b + 2; for(int k = 1; k <= n; k++) { addedge(s, k, 1); } for(int k = 1; k <= b; k++) { addedge(n + k, t, c[k]); } for(int k = 1; k <= n; k++) { for(int p = i - 1; p <= (j - 1); p++) { addedge(k, n + g[k][p], 1); } } int flow = max_flow(s, t); // printf("%d\n", flow); if(flow == n) { diff = min(diff, j - i + 1); break; } } } printf("%d\n", diff); return 0; }
741C - Arpa’s overnight party and Mehrdad’s silent entering
二分图判定问题。
关键看怎么连边,男女朋友之间连边,$i\times 2 - 1$向$i\times2$连边,构成了长度为偶数的环。(不会证明)
#include <bits/stdc++.h> using namespace std; const int maxn = 2e5 + 50; vector<int> g[maxn]; int vis[maxn]; void dfs(int u, int col) { vis[u] = col; for(int i = 0; i < (int)g[u].size(); i++) { int v = g[u][i]; if(!vis[v]) { dfs(v, 3 - col); } } } int u[maxn], v[maxn]; int main() { int n; scanf("%d", &n); for(int i = 1; i <= n; i++) { scanf("%d %d", &u[i], &v[i]); g[u[i]].push_back(v[i]); g[v[i]].push_back(u[i]); } for(int i = 1; i <= 2 * n; i += 2) { g[i].push_back(i + 1); g[i + 1].push_back(i); } for(int i = 1; i <= 2 * n; i++) { if(!vis[i]) { dfs(i, 1); } } for(int i = 1; i <= n; i++) { printf("%d %d\n", vis[u[i]], vis[v[i]]); } return 0; }