定理1(Konig 定理):最小点覆盖数 = 最大匹配数
点覆盖:点集合使得任意一条边至少有一个端点在集合中。
定理2:最大独立集 = 顶点数 - 最大匹配数
独立集:点集合中任何两个顶点都不互相连接。
定理3:最小路径覆盖数 = 顶点数 – 最大匹配数
路径覆盖:任何一个点都属于且仅属于一条路径。
HDU - 1045 - Fire Net
题目链接<http://acm.hdu.edu.cn/showproblem.php?pid=1045>
题意:
给出一张图,‘X’代表墙,‘.’ 代表空地。在空地上放一些炮塔,炮塔不能处在同一行同一列,除非被墙隔开。问最多能放多少个炮塔。
题解:
这题其实可以二进制暴力模拟去做,但也可以用二分图来做。
考虑不存在墙的情况,那么可以把行和列当作两边的点,中间放炮塔当作边,进行二分图匹配即可。换句话说,就是把每一列缩成一点,每一行缩成一点。
如果存在墙,墙的两侧其实就相当于是两个互不相关的点。而对于一个联通串(即中间不存在墙),由于只能放一个炮台的特性,所以相当于一个点。
那么就首先从列出发,对每一列的联通串缩点并染色。然后从行出发同样的对联通串缩点,并与所染色的对应列联通串连边。最后进行二分图匹配即可。
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+7;
struct Edge{
int u,v,w,nxt;
Edge(int u=0,int v=0,int w=0,int nxt=0):u(u),v(v),w(w),nxt(nxt){}
}e[N*2];
int p[N],edn;
void add(int u,int v,int w){
e[++edn]=Edge(u,v,w,p[u]);p[u]=edn;
e[++edn]=Edge(v,u,w,p[v]);p[v]=edn;
}
int match[N],vis[N];
int n,m;
bool dfs(int u){
for(int i=p[u];~i;i=e[i].nxt){
int v=e[i].v;
if(vis[v]) continue;
vis[v]=1;
if(match[v]==-1||dfs(match[v])){
match[v]=u;
return true;
}
}
return false;
}
char mp[N][N],col[N][N];
int main()
{
int n;
while(scanf("%d",&n)!=EOF){
if(n==0) break;
memset(match,-1,sizeof(match));
memset(p,-1,sizeof(p));edn=-1;
for(int i=1;i<=n;i++)
scanf("%s",mp[i]+1);
int top=1;
for(int j=1;j<=n;j++){
for(int i=1;i<=n;i++){
if(mp[i][j]=='X') top++;
else col[i][j]=top;
}
top++;
}
top=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(mp[i][j]=='X') top++;
else add(top,col[i][j]+100,1);
}
top++;
}
int ans=0;
for(int i=1;i<=top;i++){
memset(vis,0,sizeof(vis));
if(dfs(i)) ans++;
}
printf("%d\n",ans);
}
return 0;
}
HDU - 2819 - Swap
题目链接<http://acm.hdu.edu.cn/showproblem.php?pid=2819>
题意:
给出一张01矩阵,要求交换若干行若干列后使得主对角线全为1。并输出交换次数和过程。
题解:
根据线代的知识可以得知,只需考虑交换行就行。那么把行列当作点,1当作边,做一个二分图最大匹配。如果匹配书等于n那么就满足。
开一个a[i]数组记录现在第i行放的是初始的第几行。b[i]数组记录初始的第i行现在放在第几行。从头到尾扫一遍,逐一放进匹配行,记录一下交换顺序即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+7;
struct Edge{
int v,nxt;
Edge(int v=0,int nxt=0):v(v),nxt(nxt){}
}e[N*N*2];
int n,tmp;
int p[N],edn;
void add(int u,int v){
e[++edn]=Edge(v,p[u]);p[u]=edn;
}
int vis[N],match[N];
bool dfs(int u){
for(int i=p[u];~i;i=e[i].nxt){
int v=e[i].v;
if(vis[v]) continue;
vis[v]=1;
if(match[v]==-1||dfs(match[v])){
match[v]=u;
return true;
}
}
return false;
}
int pr[N][2];
int a[N],b[N];
int main(){
while(scanf("%d",&n)!=EOF){
memset(p,-1,sizeof(p));edn=-1;
memset(match,-1,sizeof(match));
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&tmp);
if(tmp) add(i,j);
}
}
int ans=0;
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
if(dfs(i)) ans++;
}
if(ans==n){
int cnt=0;
for(int i=1;i<=n;i++) a[i]=i,b[i]=i;
for(int i=1;i<=n;i++){
if(match[i]!=a[i]){
cnt++;
pr[cnt][0]=i;
pr[cnt][1]=b[match[i]];
swap(b[a[i]],b[match[i]]);
swap(a[i],a[pr[cnt][1]]);
}
}
printf("%d\n",cnt);
for(int i=1;i<=cnt;i++){
printf("R %d %d\n",pr[i][0],pr[i][1]);
}
}
else printf("-1\n");
}
}
【HK算法模板】HDU - 2389 - Rain on your Parade
题目链接<http://acm.hdu.edu.cn/showproblem.php?pid=2389>
题意:
二维平面上m个人和n把伞,一把伞只能给一个人。每个人都有自身的速度,问规定时间t内最多有几个人能拿到伞。
(1 <=t <= 5;1 <= m <= 3000;1 <= n <= 3000)
题解:
典型的二分图,人和伞作点,能在规定时间到的连一条边。
但这题的范围比较大。匈牙利算法的复杂度是,肯定会超时。
而Hopcroft-Carp适用于多点的情况,复杂度为,这样就能过了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e3+7;
const int M=1e7+7;
const int inf=1<<28;
struct Edge{
int v,nxt;
Edge(int v=0,int nxt=0):v(v),nxt(nxt){}
}e[M];
int p[N],edn;
void add(int u,int v){
e[++edn]=Edge(v,p[u]);p[u]=edn;
}
int mx[N],my[N],dx[N],dy[N],dis,n,m;
bool vis[N];
bool bfs(){
queue<int>q;
dis=inf;
memset(dx,-1,sizeof(dx));
memset(dy,-1,sizeof(dy));
for(int i=0;i<n;i++){
if(mx[i]==-1){
q.push(i);
dx[i]=0;
}
}
while(!q.empty()){
int u=q.front();
q.pop();
if(dx[u]>dis) break;
for(int i=p[u];~i;i=e[i].nxt){
int v=e[i].v;
if(dy[v]==-1){
dy[v]=dx[u]+1;
if(my[v]==-1) dis=dy[v];
else{
dx[my[v]]=dy[v]+1;
q.push(my[v]);
}
}
}
}
return dis!=inf;
}
bool dfs(int u){
for(int i=p[u];~i;i=e[i].nxt){
int v=e[i].v;
if(!vis[v]&&dy[v]==dx[u]+1){
vis[v]=1;
if(my[v]!=-1&&dy[v]==dis) continue;
if(my[v]==-1||dfs(my[v])){
my[v]=u;
mx[u]=v;
return 1;
}
}
}
return 0;
}
int hk(){
int res=0;
memset(mx,-1,sizeof(mx));
memset(my,-1,sizeof(my));
while(bfs()){
memset(vis,0,sizeof(vis));
for(int i=0;i<n;i++){
if(mx[i]==-1&&dfs(i)) res++;
}
}
return res;
}
struct Node{
int x,y,z;
}a[N],b[N];
int main(){
int t,cs=0,lim;
scanf("%d",&t);
while(t--){
memset(p,-1,sizeof(p));edn=-1;
scanf("%d",&lim);
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
scanf("%d",&m);
for(int i=0;i<m;i++)
scanf("%d%d",&b[i].x,&b[i].y);
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if((a[i].x-b[j].x)*(a[i].x-b[j].x)+(a[i].y-b[j].y)*(a[i].y-b[j].y)<=lim*a[i].z*lim*a[i].z){
add(i,j);
}
}
}
printf("Scenario #%d:\n%d\n\n",++cs,hk());
}
}
HDU - 4185 - Oil Skimming
题目链接<http://acm.hdu.edu.cn/showproblem.php?pid=4185>
题意:
一张n*n的二维图,由‘#’和‘.’构成。要求用尽可能多的1*2的矩形去覆盖‘#’,矩形内不能含有‘.’,问最多能覆盖多少个矩形。
注意:这题的n没有题面说的那么大。
题解:
每一个‘#’就是一个点,相邻的‘#’互相连接。由于只与相邻的连接,所以肯定是二分图,跑一边就行了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+7;
const int inf=1<<28;
struct Edge{
int v,nxt;
Edge(int v=0,int nxt=0):v(v),nxt(nxt){}
}e[N*4];
int p[N],edn;
void add(int u,int v){
e[++edn]=Edge(v,p[u]);p[u]=edn;
}
char s[N][N];
int mp[N][N];
int vis[N],match[N];
bool dfs(int u){
for(int i=p[u];~i;i=e[i].nxt){
int v=e[i].v;
if(vis[v]) continue;
vis[v]=1;
if(match[v]==-1||dfs(match[v])){
match[v]=u;
return true;
}
}
return false;
}
int dir[4][2]={1,0,-1,0,0,1,0,-1};
int main(){
int t,cs=0,n;
scanf("%d",&t);
while(t--){
memset(p,-1,sizeof(p));edn=-1;
memset(match,-1,sizeof(match));
memset(mp,0,sizeof(mp));
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%s",s[i]+1);
int top=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(s[i][j]=='#') mp[i][j]=++top;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(mp[i][j]){
for(int k=0;k<4;k++){
int di=i+dir[k][0];
int dj=j+dir[k][1];
if(mp[di][dj]){
add(mp[i][j],mp[di][dj]);
}
}
}
}
}
int ans=0;
for(int i=1;i<=top;i++){
memset(vis,0,sizeof(vis));
if(dfs(i)) ans++;
}
printf("Case %d: %d\n",++cs,ans/2);
}
}
【二分图染色判断奇环】Codeforces - 557D - Vitaly and Cycle
题目链接<http://codeforces.com/problemset/problem/557/D>
题意:
在一张无向图中,最少添加多少边,能使得图包含奇环(存在一个环包含奇数个数的点)。并计算出有多少种添边的方案。
题解:
对于本题可以分情况讨论
1)如果图中本身就没有边,那么可以任选三个点连3条边。一共有
种方案。
2)如果图中本身就存在奇环,那么答案就是0 1。
关于奇环的判断可以利用黑白染色的进行搜索,如果一条边连了两个同色的那么就存在奇环。
如果不存在奇环,该图就是个二分图。而二分图也分了两种情况。
3)如果图中每个点都最多只连了一条边,也就是每个点的度数都小于等于1。那么只需选出一条边,另外再选一个点,连成三角形即可。所需边数是2,方案数是m*(n-2)。
4)对于每一个连通块进行黑白染色后,黑点与黑点连一条边,白点与白点连一条边也就能构造出奇环。所需添加的边数就是1,每一个连通块对答案的贡献是
#include<iostream>
#include<cstdio>
#include<string.h>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const ll N=1e6+7;
ll p[N],edn;
ll num[3],vis[N];
struct Edge{
ll u,v,nxt;
Edge(ll u=0,ll v=0,ll nxt=0):u(u),v(v),nxt(nxt){}
}e[N*2];
void add(ll u,ll v){
e[++edn]=Edge(u,v,p[u]);p[u]=edn;
e[++edn]=Edge(v,u,p[v]);p[v]=edn;
}
bool dfs(ll u){
num[vis[u]]++;
for(ll i=p[u];~i;i=e[i].nxt){
ll v=e[i].v;
if(vis[v]){
if(vis[v]==vis[u]){
return false;
}
continue;
}
vis[v]=3-vis[u];
if(dfs(v)==false) return false;
}
return true;
}
int main()
{
ll n,m,u,v;
scanf("%lld%lld",&n,&m);
if(m==0) printf("3 %lld\n",n*(n-1)*(n-2)/6);
else{
memset(p,-1,sizeof(p));edn=-1;
for(ll i=1;i<=m;i++){
scanf("%lld%lld",&u,&v);
add(u,v);
}
ll ans=0;
memset(vis,0,sizeof(vis));
bool flag=true;
for(ll i=1;i<=n;i++){
if(!vis[i]){
num[1]=num[2]=0;
vis[i]=1;
flag=dfs(i);
if(flag==false) break;
ans=ans+num[1]*(num[1]-1)/2+num[2]*(num[2]-1)/2;
}
}
if(flag){
if(ans==0) printf("2 %lld\n",m*(n-2));
else printf("1 %lld\n",ans);
}
else printf("0 1\n");
}
}
POJ - 2594 - Treasure Exploration
题目链接<http://poj.org/problem?id=2594>
题意:
在一张有向无环图中,放置若干个机器人,每个机器人都有一个属于自己的路径。图中每一个节点都可以属于多个路径。问最少需要投放几个机器人才能遍历完图中每个节点。
题解:
粗一看以为是最小路径覆盖裸题,直接wa掉。
因为节点可以属于多个路径,所以不能裸着做,但只需另外floyd一下,把下一个节点能到的点直接连上就行了。
这题还是用矩阵好实现。
#include<iostream>
#include<string.h>
#include<algorithm>
#include<cstdio>
using namespace std;
typedef long long ll;
const int N=5e2+7;
const int inf=1<<28;
int mp[N][N];
int vis[N],match[N];
int n,m,u,v;
bool dfs(int u){
for(int v=1;v<=n;v++){
if(mp[u][v]&&!vis[v]){
vis[v]=1;
if(match[v]==-1||dfs(match[v])){
match[v]=u;
return true;
}
}
}
return false;
}
int main(){
while(scanf("%d%d",&n,&m)!=EOF){
if(n==0&&m==0) break;
memset(match,-1,sizeof(match));
memset(mp,0,sizeof(mp));
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
mp[u][v]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(!mp[i][j]) continue;
for(int k=1;k<=n;k++){
if(!mp[j][k]) continue;
mp[i][k]=1;
}
}
}
int ans=0;
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
if(dfs(i)) ans++;
}
printf("%d\n",n-ans);
}
}
HDU - 3829 -Cat VS Dog
题目链接<http://acm.hdu.edu.cn/showproblem.php?pid=3829>
题意:
动物园有若干只狗和猫,每个小朋友都要么喜欢一只狗不喜欢一只猫,要么反过来。现在要移除一些狗和猫,小朋友只有在不移除自己喜欢的动物,移除了自己不喜欢的动物才会开心。问最多能让几个小朋友开心。
题解:
如果把猫和狗当作点怕是没法做的。
其实这题的矛盾在人,如果某个人喜欢的动物另一个人不喜欢,那么这两个人就只能满足一个。
所以可以把人当作节点,矛盾的人连边,做最大独立集即可。
还有没必要尝试读字符串里的数字,数字很有可能到超过个位。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e2+7;
struct Edge{
int v,nxt;
Edge(int v=0,int nxt=0):v(v),nxt(nxt){}
}e[N*N];
int n,tmp;
int p[N],edn;
void add(int u,int v){
e[++edn]=Edge(v,p[u]);p[u]=edn;
e[++edn]=Edge(u,p[v]);p[v]=edn;
}
int vis[N],match[N];
bool dfs(int u){
for(int i=p[u];~i;i=e[i].nxt){
int v=e[i].v;
if(vis[v]) continue;
vis[v]=1;
if(match[v]==-1||dfs(match[v])){
match[v]=u;
return true;
}
}
return false;
}
struct Node{
char x[5],y[5];
}a[N];
char s1[5],s2[5];
int main(){
int n,m,k;
while(scanf("%d%d%d",&n,&m,&k)!=EOF){
memset(p,-1,sizeof(p));edn=-1;
memset(match,-1,sizeof(match));
for(int i=1;i<=k;i++)
scanf("%s%s",a[i].x,a[i].y);
for(int i=1;i<=k;i++)
for(int j=i+1;j<=k;j++)
if(strcmp(a[j].x,a[i].y)==0||strcmp(a[j].y,a[i].x)==0)
add(i,j);
int ans=0;
for(int i=1;i<=k;i++){
memset(vis,0,sizeof(vis));
if(dfs(i)) ans++;
}
printf("%d\n",k-ans/2);
}
}