P2774 方格取数问题
二分图最小点覆盖和最大独立集都可以转化为最大匹配求解。在这个基础上,把每个点赋予一个非负的权值,这两个问题就转化为:二分图最小点权覆盖和二分图最大点权独立集。
二分图最小点权覆盖
从x或者y集合中选取一些点,使这些点覆盖所有的边,并且选出来的点的权值尽可能小。
建模:
原二分图中的边(u,v)替换为容量为INF的有向边(u,v),设立源点s和汇点t,将s和x集合中的点相连,容量为该点的权值;将y中的点同t相连,容量为该点的权值。在新图上求最大流,最大流量即为最小点权覆盖的权值和。
二分图最大点权独立集
在二分图中找到权值和最大的点集,使得它们之间两两没有边。其实它是最小点权覆盖的对偶问题。答案=总权值-最小点覆盖集。具体证明参考胡波涛的论文。
最小点权覆盖问题
如图建模,求最小割,应该中间的边为inf是无法割掉的所有只能割掉左右的边,而且一条边只会割掉权值最小的点,这样跑完网络流求最小割,就是最小点权覆盖集
而最大点权独立集 = 全集 - 最小点权覆盖集
//最小点权覆盖集:二分图中每条边的端点至少选一个的权值最小的集合
// 最大点权独立集合:二分图中每条边之选一个端点的权值最大的集合 = 图中所有权值 - 最小点权覆盖集
/
#include<cstring>
#include<queue>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
#include<cmath>
#include<string>
#define ll long long
#define max(a,b) a > b ? a : b
#define min(a,b) a < b ? a : b
#define me(a,b) memset(a, b, sizeof(a))
#define iosos ios::sync_with_stdio(false)
#define fo(a,b,c) for(int a = b; a < c; a++)
using namespace std;
const int maxn = 1e4 + 10;
const int maxm = maxn << 1;
const int inf = 0x7f7f7f7f;
int n, m, s, t, p;
int h[maxn], cur[maxn], dep[maxn];
int cell[101][101];
bool vis[101][101];
int dx[4] = {1,-1,0,0};
int dy[4] = {0,0,-1,1};
struct Edge {
int nes, to, w;
}e[maxn];
inline void add(int u, int v, int l) {
e[p].nes = h[u], e[p].to = v, e[p].w = l, h[u] = p++;
}
inline void addedge(int u, int v, int l) {
add(u, v, l);
add(v, u, 0);
}
int BFS(int s, int t) {
queue<int> q;
me(dep, 0x7f);
dep[s] = 0;
q.push(s);
while(!q.empty()) {
int u = q.front();q.pop();
if(u == t) break;
for(int i = h[u]; ~i; i =e[i].nes) {
int v = e[i].to;
int w = e[i].w;
if(w > 0 && dep[v] > dep[u] + 1) {
dep[v] = dep[u] + 1;
q.push(v);
}
}
}
return dep[t] != inf;
}
int DFS(int s, int mw) {
if(s == t) return mw;
for(int i = cur[s]; ~i; i = e[i].nes, cur[s] = i) {
int v = e[i].to;
int w = e[i].w;
if(w <= 0 || dep[v] != dep[s] + 1) continue;
int cw = DFS(v, min(mw, w));
if(cw == 0) continue;
e[i].w -= cw;
e[i^ 1].w += cw;
return cw;
}
return 0;
}
inline void curr() {
fo(i, 0, maxn) cur[i] = h[i];
}
int dinic() {
int tot = 0, d;
while(BFS(s, t)) {
curr();
while(d = DFS(s, inf)) tot += d;
}
return tot;
}
int main() {
iosos;
me(h, - 1);
ll sum = 0;
cin >> n >> m;
fo(i, 0, n) {
fo(j, 0, m) {
cin>>cell[i][j];
sum += cell[i][j];
}
}
s = 0, t = n*m + 1;
fo(i, 0, n) {
fo(j, 0, m) {
if(!vis[i][j]) {
int u = i * m + j + 1;
addedge(s, u, cell[i][j]); // 源点
fo(z, 0, 4) {
int x = i+dx[z], y = j + dy[z];
if(0 <= x && x < n && y >= 0&& y < m) {
int v = x * m + y + 1;
addedge(u, v, inf);
vis[x][y] = true;
}
}
} else {
addedge(i * m + j + 1, t, cell[i][j]);
}
}
}
int min_sum = dinic();
cout << sum - min_sum;
return 0;
}
P2756 飞行员配对方案问题
二分图最大匹配问题
如图建模, 边权全为1, 这样每个点最多选择一条边,而且每个点也最多被选一次,也就是说每个点只能与一个点匹配。这样最从s流向t的最大流量,流量有多大就说明有几条边被匹配,而且所有边都没有重复的点。
P2764 最小路径覆盖问题
拆点建模, 把点拆成2个点,1~n 拆成 1 ~2n
然后对应u-v, 连u-v’,这样就构成了二分图
然后求此图的最大匹配,我们可以假象,图没有边的时候可以看作n个边,每个点都是一条边。最短路径覆盖每个点是只能使用一次的路径, 所以每个点最多链接一个点,也最多只能被一个点链接,求此二分图的最大匹配, 就是说从x1集合出发每个点每次只能选一个点匹配,而x1‘集合的点也只能被匹配一次,对应就是u->v。 u只能匹配一个v, v 只能被一个u匹配,所以求此图最大匹配数就是能匹配多少对u->v, 而每个被匹配的边是一定连同的所以可以看作一个点那么没得到一个匹配边就会点少1,所以:
最短路径覆盖条数 = n - 拆点二分图最大匹配
#include<cstring>
#include<queue>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
#include<cmath>
#include<string>
#define ll long long
#define max(a,b) a > b ? a : b
#define min(a,b) a < b ? a : b
#define me(a,b) memset(a, b, sizeof(a))
#define iosos ios::sync_with_stdio(false)
#define fo(a,b,c) for(int a = b; a < c; a++)
using namespace std;
const int maxn = 2*1e4 + 10;
const int maxm = 3*1e4;
const int inf = 0x7f7f7f7f;
int h[maxn], cur[maxn], dep[maxn], fro[maxn];
int deg[maxn];
int n, m, s, t, p;
struct Edge {
int nes, to, w;
}e[maxn];
inline void add(int u, int v, int l) {
e[p].nes = h[u], e[p].to = v, e[p].w = l, h[u] = p++;
}
inline void addedge(int u, int v, int l ) {
add(u, v, l);
add(v, u, 0);
}
int DFS(int s ,int mw) {
if(s == t) return mw;
for(int i = cur[s]; ~i; i = e[i].nes, cur[s] = i) {
int v = e[i].to;
int w = e[i].w;
if(w <= 0 || dep[v] != dep[s] + 1) continue;
int cw = DFS(v, min(mw, w));
if(cw == 0) continue;
e[i].w -= cw;
e[i^1].w += cw;
return cw;
}
return 0;
}
int BFS(int s, int t) {
queue<int> q;
me(dep, 0x7f);
dep[s] = 0;
q.push(s);
while(!q.empty()) {
int u = q.front(); q.pop();
if(u == t) break;
for(int i = h[u]; ~i; i = e[i].nes) {
int v = e[i].to;
int w = e[i].w;
if(w > 0 && dep[v] > dep[u] + 1) {
dep[v] = dep[u] + 1;
q.push(v);
}
}
}
return dep[t] != inf;
}
inline void curr() {
fo(i, 0, maxn) cur[i] = h[i];
}
int dinic() {
int tot = 0, d;
while(BFS(s, t)) {
curr();
while(d = DFS(s, inf)) {
tot += d;
}
}
return tot;
}
bool cmp(vector<int> a, vector<int> b) {
return a.size() > b.size();
}
int main() {
iosos;
me(h, -1);
cin>>n>>m;
while(m--) {
int u, v;
cin>>u>>v;
addedge(u, v+n, 1); //u 到 v‘
}
s=0,t=n*2+1;
for(int i = 1; i <= n; i++) { //建立超级汇,与超级源
addedge(s, i, 1); //super power
addedge(i+n, t, 1);
}
int ans=n - dinic();
me(fro, -1);
for(int i = 1; i <= n; i++) {
for(int j = h[i]; ~j; j = e[j].nes) {
int v = e[j].to;
int w = e[j].w;
if(w == 0 && v != s) {
fro[i] = v-n;
deg[v-n]++;
}
}
}
vector<int> path[maxn];
int cut = 0;
for(int i = 1; i <= n; i++) {
if(fro[i] != -1 && deg[i] == 0) {
int t = i;
while(~t) {
path[cut].push_back(t);
t = fro[t];
}
cut++;
}
}
sort(path, path + cut, cmp);
for(int i = 0; i < cut; i++) {
int len = path[i].size();
for(int j = 0; j < path[i].size(); j++) {
cout<<path[i][j];
if(j < len-1) cout<<' ';
}
cout<<endl;
}
cout<<ans;
return 0;
}