A->L:二分图最大匹配
二分图最大匹配最常用的是匈牙利算法,用DFS找增广路,时间复杂度O(n*m),在稠密图中简单又好用。当数据范围较大时,可以选用Hopcroft_Carp算法,BFS多次增广,优化之后的时间复杂度为O(sqrt(n)*m)。
几条重要的性质:
最大匹配=最小顶点覆盖
DAG最小路径覆盖=顶点数-最大匹配数
最大独立集=顶点数-最大匹配数
A. Fire Net -HDU1045: 每选择一个点,和这个点同行或同列没有被墙挡住的点就都不能选择。
难点在于建图。根据这个图的性质,每一行中没有被墙分开的所有点合并,算一行。例如....#....在建图时算两行,....#...#..算三行。对列也进行相同的处理。然后求最大匹配即可。
D. 棋盘游戏 -HDU1281: 基于A题的规则,存在某些“关键点”,只要不选择这些点,就无法选择最多的点。求关键点的数目。
n*m<=1e4,枚举每个可以选择的点,将其取消后再计算最大匹配,如果与原图的最大匹配不相等则该点是一个关键点。
E. Swap -HDU2819: 给出一个0-1矩阵,要求计算一个方案,交换矩阵的某些行或某些列,使得矩阵左对角线全为1。
通过类比矩阵的秩,可以得到:如果只进行行变换时无解,那么只进行列变换或行列都变换也无解。计算原图的最小点覆盖即最大匹配,如果不等于n则无解。在记录匹配路径时,从前往后扫,如果linker[i]!=i则是一条路径,同时用linker[i]的信息进行交换使得linker[i]==i。
F. Rain on Your Parade -HDU2389: 这个用匈牙利会超时。。可以用Hopcroft_Carp算法。
H. Antenna Placement -POJ3020: 题意略坑,基站只能放在城市,但信号覆盖范围可以包括空地,在建图时也不需要向空地连边。只考虑所有的城市求最大独立集即可。
K. Treasure Exploration -POJ2594: 给定DAG,求最小路径覆盖。
坑点是如果DAG中的有向边有相交情况,那么就不能直接求最大匹配。需要先用Floyd求一个传递闭包,可以认为是类似路径压缩的操作。然后再求最大匹配。
L. Cat VS Dog -HDU3829: 有一群猫、狗、人。每个人讨厌一只动物并喜欢一只动物。现需要选择一些动物,如果某人喜欢的被选中而不喜欢的没选中,他就会高兴。求最多可使多少人高兴。
个人认为建图比较骚。方法是如果有两人喜欢的动物恰好是对方不喜欢的,就在这两个人之间连边。答案即为新图的最小顶点覆盖。
Hopcroft_Carp算法模板(以F-HDU2389为例):
#include<bits/stdc++.h>
#define maxn 3050
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
int T,t,n,m;
vector<int>maze[maxn];
int linkx[maxn],linky[maxn],vis[maxn];
int v[maxn],dis,dx[maxn],dy[maxn];
struct node
{
int x,y;
}e[maxn],w[maxn];
bool bfs()
{
dis=INF;
memset(dx,-1,sizeof(dx));
memset(dy,-1,sizeof(dy));
queue<int>q;
for(int i=1;i<=m;i++)
{
if(linkx[i]==-1)
{
q.push(i);
dx[i]=0;
}
}
while(!q.empty())
{
int u=q.front();
q.pop();
if(dx[u]>dis)break;
int len=maze[u].size();
for(int i=0;i<len;i++)
{
int v=maze[u][i];
if(dy[v]==-1)
{
dy[v]=dx[u]+1;
if(linky[v]==-1)dis=dy[v];
else
{
dx[linky[v]]=dy[v]+1;
q.push(linky[v]);
}
}
}
}
return (dis!=INF);
}
bool dfs(int u)
{
int len=maze[u].size();
for(int i=0;i<len;i++)
{
int v=maze[u][i];
if(!vis[v]&&dy[v]==dx[u]+1)
{
vis[v]=1;
if(linky[v]!=-1&&dy[v]==dis)continue;
if(linky[v]==-1||dfs(linky[v]))
{
linky[v]=u;
linkx[u]=v;
return true;
}
}
}
return false;
}
int hopcroft_karp()
{
int res=0;
memset(linkx,-1,sizeof(linkx));
memset(linky,-1,sizeof(linky));
while(bfs())
{
memset(vis,0,sizeof(vis));
for(int i=1;i<=m;i++)
{
if(linkx[i]==-1&&dfs(i))
res++;
}
}
return res;
}
int main()
{
scanf("%d",&T);
int kase=0;
while(T--)
{
scanf("%d%d",&t,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&e[i].x,&e[i].y,&v[i]);
}
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&w[i].x,&w[i].y);
}
for(int i=1;i<=m;i++)
maze[i].clear();
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
int len=abs(e[i].x-w[j].x)+abs(e[i].y-w[j].y);
if(len<=t*v[i])
maze[i].push_back(j);
}
}
int ans=hopcroft_karp();
printf("Scenario #%d:\n%d\n\n",++kase,ans);
}
return 0;
}
M->O:二分图多重匹配
多重匹配即某些点可以和多条匹配边相连。方法是:设立一个超级源点S和超级汇点T,从S向原图X部每个点、从原图Y部每个点向T都连一条容量为相应顶点容量的边,原图各边保留容量为1。原图的多重最大匹配即为新图从S到T的最大流。
N. Optimal Milking -POJ2112: 在二分图多重匹配的模型中,求达到最大匹配时最长匹配边的最小值。
最大值最小,显然需要二分地求解。用Floyd预处理出各点之间的最短距离,每次用mid判断时都去掉原图中长度大于mid的边,看最大匹配是否发生变化。
二分图多重匹配模板(以N-POJ2112为例):
#include<bits/stdc++.h>
#define maxn 255
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
int n,m,k,no;
int dis[maxn][maxn];
int level[maxn],iter[maxn];
struct node
{
int to;
int cap,rev;
node(int a,int b,int c){to=a;cap=b;rev=c;}
};
vector<node>maze[maxn];
void add(int a,int b,int x)
{
maze[a].push_back(node(b,x,maze[b].size()));
maze[b].push_back(node(a,0,maze[a].size()-1));
}
void floyd()
{
for(int k=1;k<=n+m;k++)
{
for(int i=1;i<=n+m;i++)
{
for(int j=1;j<=n+m;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
}
void bfs(int s)
{
queue<int>q;
memset(level,-1,sizeof(level));
level[s]=0;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=0;i<maze[u].size();i++)
{
node &tmp=maze[u][i];
int v=tmp.to;
if(tmp.cap>0&&level[v]<0)
{
level[v]=level[u]+1;
q.push(v);
}
}
}
}
int dfs(int s,int t,int f)
{
if(s==t)return f;
for(int &i=iter[s];i<maze[s].size();i++)
{
node &tmp=maze[s][i];
int v=tmp.to;
if(tmp.cap>0&&level[s]<level[v])
{
int cnt=dfs(v,t,min(f,tmp.cap));
if(cnt>0)
{
tmp.cap-=cnt;
maze[v][tmp.rev].cap+=cnt;
return cnt;
}
}
}
return 0;
}
int dinic(int s,int t)
{
int ans=0;
while(1)
{
bfs(s);
if(level[t]<0)break;
memset(iter,0,sizeof(iter));
int tmp;
while((tmp=dfs(s,t,INF))>0)
ans+=tmp;
}
return ans;
}
bool judge(int x)
{
int s=0,t=n+m+1;
for(int i=0;i<=t;i++)
maze[i].clear();
for(int i=1;i<=n;i++)
add(s,i,k);
for(int i=n+1;i<=n+m;i++)
add(i,t,1);
for(int i=1;i<=n;i++)
{
for(int j=n+1;j<=n+m;j++)
{
if(dis[i][j]<=x)
add(i,j,1);
}
}
int ans=dinic(s,t);
if(ans==m)return true;
return false;
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n+m;i++)
{
for(int j=1;j<=n+m;j++)
{
scanf("%d",&dis[i][j]);
if(dis[i][j]==0)
dis[i][j]=INF;
if(i==j)dis[i][j]=0;
}
}
floyd();
int l=0,r=INF;
while(r>l+1)
{
int mid=(l+r)>>1;
if(judge(mid))r=mid;
else l=mid;
}
printf("%d\n",r);
return 0;
}
P. 奔小康赚大钱 -HDU2255: 二分图最大权匹配
最大权匹配使用KM算法,时间复杂度O(nx^2*ny)。
#include<bits/stdc++.h>
#define maxn 305
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
int n,nx,ny;
int maze[maxn][maxn];
int linker[maxn],lx[maxn],ly[maxn];
int slack[maxn],visx[maxn],visy[maxn];
bool dfs(int u)
{
visx[u]=1;
for(int v=1;v<=ny;v++)
{
if(visy[v])continue;
int tmp=lx[u]+ly[v]-maze[u][v];
if(tmp==0)
{
visy[v]=1;
if(linker[v]==-1||dfs(linker[v]))
{
linker[v]=u;
return true;
}
}
else if(slack[v]>tmp)
slack[v]=tmp;
}
return false;
}
int km()
{
memset(linker,-1,sizeof(linker));
memset(ly,0,sizeof(ly));
for(int i=1;i<=nx;i++)
{
lx[i]=-INF;
for(int j=1;j<=ny;j++)
lx[i]=max(lx[i],maze[i][j]);
}
for(int u=1;u<=nx;u++)
{
for(int i=1;i<=ny;i++)
slack[i]=INF;
while(1)
{
memset(visx,0,sizeof(visx));
memset(visy,0,sizeof(visy));
if(dfs(u))break;
int tmp=INF;
for(int i=1;i<=ny;i++)
{
if(!visy[i]&&tmp>slack[i])
tmp=slack[i];
}
for(int i=1;i<=nx;i++)
{
if(visx[i])
lx[i]-=tmp;
}
for(int i=1;i<=ny;i++)
{
if(visy[i])ly[i]+=tmp;
else slack[i]-=tmp;
}
}
}
int res=0;
for(int i=1;i<=ny;i++)
{
if(linker[i]!=-1)
res+=maze[linker[i]][i];
}
return res;
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
scanf("%d",&maze[i][j]);
}
nx=ny=n;
printf("%d\n",km());
}
return 0;
}
Q. Tour -HDU3488: 有向环最小权覆盖
方法是:把每个点拆成左点i和右点i+n。超级源点和左点连边,超级汇点和右点连边,原图边保留从相应左点到相应右点的边。从S到T跑最小费用最大流,答案即为新图的最小费用。
#include<bits/stdc++.h>
#define maxn 405
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
int t,n,m,no,a,b,x;
int head[maxn],pre[maxn],dis[maxn];
int vis[maxn],maze[maxn][maxn];
struct node
{
int to,nxt;
int cap,flow,cost;
}e[maxn*maxn];
void add(int from,int to,int cap,int cost)
{
e[no].to=to;
e[no].nxt=head[from];
e[no].flow=0;
e[no].cap=cap;
e[no].cost=cost;
head[from]=no++;
}
void init()
{
memset(head,-1,sizeof(head));
no=0;
for(int i=0;i<maxn;i++)
{
for(int j=0;j<maxn;j++)
maze[i][j]=INF;
}
}
bool spfa(int s,int t)
{
queue<int>q;
memset(vis,0,sizeof(vis));
memset(pre,-1,sizeof(pre));
for(int i=0;i<=t;i++)dis[i]=INF;
q.push(s);
dis[s]=0,vis[s]=1;
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i!=-1;i=e[i].nxt)
{
int v=e[i].to;
if(e[i].cap>e[i].flow&&dis[v]>dis[u]+e[i].cost)
{
pre[v]=i;
dis[v]=dis[u]+e[i].cost;
if(!vis[v])
{
vis[v]=1;
q.push(v);
}
}
}
}
if(pre[t]==-1)return false;
return true;
}
void MinCostMaxFlow(int s,int t,int &cost,int &flow)
{
cost=flow=0;
while(spfa(s,t))
{
int minn=INF;
for(int i=pre[t];i!=-1;i=pre[e[i^1].to])
{
if(minn>e[i].cap-e[i].flow)
minn=e[i].cap-e[i].flow;
}
for(int i=pre[t];i!=-1;i=pre[e[i^1].to])
{
e[i].flow+=minn;
e[i^1].flow-=minn;
cost+=(e[i].cost*minn);
}
flow+=minn;
}
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
init();
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&a,&b,&x);
if(x<maze[a][b])
maze[a][b]=x;
}
int s=0,t=2*n+1;
for(int i=1;i<=n;i++)
{
add(s,i,1,0);
add(i,s,0,0);
for(int j=1;j<=n;j++)
{
if(i==j)continue;
if(maze[i][j]<INF)
{
add(i,j+n,1,maze[i][j]);
add(j+n,i,0,-maze[i][j]);
}
}
add(i+n,t,1,0);
add(t,i+n,0,0);
}
int cost,flow;
MinCostMaxFlow(s,t,cost,flow);
printf("%d\n",cost);
}
return 0;
}
R->S: 一般图匹配带花树
对一个一般图求最大匹配基本上是n次寻找增广路然而窝并不知道具体是个什么东西orz大概要止于抄模板了
R. Work Scheduling -URAL1099
#include<bits/stdc++.h>
#define maxn 405
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
int n,s,t,ans,a,b,newbase;
int fa[maxn],base[maxn];
int linker[maxn],maze[maxn][maxn];
bool inque[maxn],inpa[maxn],inblo[maxn];
queue<int>q;
int lca(int u,int v)
{
memset(inpa,0,sizeof(inpa));
while(1)
{
u=base[u];
inpa[u]=1;
if(u==s)break;
u=fa[linker[u]];
}
while(1)
{
v=base[v];
if(inpa[v])break;
v=fa[linker[v]];
}
return v;
}
void reset(int u)
{
int v;
while(base[u]!=newbase)
{
v=linker[u];
inblo[base[u]]=inblo[base[v]]=1;
u=fa[v];
if(base[u]!=newbase)fa[u]=v;
}
}
void blossom(int u,int v)
{
newbase=lca(u,v);
memset(inblo,0,sizeof(inblo));
reset(u);
reset(v);
if(base[u]!=newbase)fa[u]=v;
if(base[v]!=newbase)fa[v]=u;
for(int i=1;i<=n;i++)
{
if(inblo[base[i]])
{
base[i]=newbase;
if(!inque[i])q.push(i);
}
}
}
void findaug()
{
memset(inque,0,sizeof(inque));
memset(fa,0,sizeof(fa));
for(int i=1;i<=n;i++)
base[i]=i;
while(!q.empty())
q.pop();
q.push(s);
inque[s]=1;
t=0;
while(!q.empty())
{
int u=q.front();
q.pop();
inque[u]=1;
for(int v=1;v<=n;v++)
{
if(maze[u][v]&&base[u]!=base[v]&&linker[u]!=v)
{
if(v==s||(linker[v]>0&&fa[linker[v]]>0))
blossom(u,v);
else if(fa[v]==0)
{
fa[v]=u;
if(linker[v]>0)
{
q.push(linker[v]);
inque[linker[v]]=1;
}
else
{
t=v;
return;
}
}
}
}
}
}
void aug()
{
int u,v,w;
u=t;
while(u>0)
{
v=fa[u];
w=linker[v];
linker[v]=u;
linker[u]=v;
u=w;
}
}
void solve()
{
memset(linker,-1,sizeof(linker));
for(int i=1;i<=n;i++)
{
if(linker[i]<0)
{
s=i;
findaug();
if(t>0)aug();
}
}
}
int main()
{
scanf("%d",&n);
memset(maze,0,sizeof(maze));
while((scanf("%d%d",&a,&b))==2)
//for(int i=0;i<3;i++){scanf("%d%d",&a,&b);
maze[a][b]=maze[b][a]=1;
solve();
ans=0;
for(int u=1;u<=n;u++)
{
if(linker[u]>0)
ans++;
}
printf("%d\n",ans);
for(int u=1;u<=n;u++)
{
if(u<linker[u])
printf("%d %d\n",u,linker[u]);
}
return 0;
}