前言
主要说两道题。
二分图最大匹配
可以用匈牙利算法O(nm)解决,可以用dinic算法O(n√m)解决,更快的话可以预流推进。
一般来说匈牙利足够了。
然后说一下匈牙利的代码:(10个月没写过dinic了,有空复习一下)
#include<iostream>
#include<vector>
using namespace std;
int n,m,e;
vector<vector<int>> a;
int vis[505],match[505];
int ans;
bool dfs(int u) {
for(auto&v:a[u]) {
if(vis[v]) continue;
vis[v]=true;
if(!match[v]||dfs(match[v])) {
match[v]=u;
return true;
}
}
return false;
}
int main() {
cin>>n>>m>>e;
for(int i=0;i<=n;i++) a.push_back({});
while(e--) {
int u,v;
cin>>u>>v;
a[u].push_back(v);
}
// for(int i=1;cout<<i<<':',i<=n;i++,cout<<endl)
// for(auto&j:a[i])
// cout<<j<<' ';
for(int i=1;i<=n;i++) {
for(auto&j:vis) j=0;
if(dfs(i))
ans++;
}
cout<<ans;
return 0;
}
主要的细节就是,注意vis数组是每进行一轮dfs,都要清空一次,这意味着每一个左侧点可以在不同的轮中匹配同一个右侧节点,而在同一轮中不可以。
这是由于,在每加入一个新的节点匹配,所有左侧节点最多重新匹配一次,如果不标记则会陷入死循环。而在多轮中,由于节点u原来出让的节点v的match[x]还可能可以将节点v再次出让给节点u,进而完成匹配,所以可以在不同轮中重复匹配一个节点。
简单建图与矩阵技巧
例题1
结论很好猜,难的是证明。先证明一下这道题目的结论:
最终状态是(1,1)(2,2)…(n,n)都有一个点
我们把点看成匹配边的话,就是每行和每列都做到了匹配
换言之就是N个行和N个列都有匹配时,一定能转换成最终状态
所以就如S向每行所对应的点连边,每列所对应的点向T连边
每个1的块就是某行和某列的边
再逆过来转换到初始状态,我们发现交换行本质就是交换S向这两行连的边,所以匹配数不变
同理交换列也是
所以只要按照最初状态建二分图跑最大流,就是可能的最终状态的最大流
当且仅当最大流是N的时候可以转换到最终状态
——Night_Aurora
矩阵技巧
然后这道题用到了一个经典的关于矩阵的结论:
对于矩阵:
行交换操作:选择矩阵的任意两行,交换这两行
列交换操作:选择矩阵的任意两列,交换这两列
交换两行时,这两行内的数字相对位置关系不变。
交换两列时,这两列内的数字相对位置关系不变。
任意两次行列操作都不互相影响,归纳可以得出,任何次数的行列都操作互不影响。
也就是说,可以先交换行,再交换列。
同样用到类似技巧的还有这道题。
此类矩阵问题建图,主要涉及三个方面:连通性(主要是SCC),二分图和网络流。
后话
于是皆大欢喜。