最近重新学习了二分匹配的算法,并趁热打铁做了几道简单的题。
这个博客讲的二分匹配的算法比较通俗易懂,https://blog.youkuaiyun.com/dark_scope/article/details/8880547
把每一个任务在两种机器下的模式看作一条边,建图之后求最小顶点覆盖集(用最少的点,使每一条边都与其中一个点相关联),而最小顶点覆盖集=最大匹配,此题在输入的时候把模式为0的去掉,因为两种机器初始状态为0。
#include <stdio.h>
#include <string.h>
const int maxn = 110;
int G[maxn][maxn],vis[maxn],match[maxn];
int nx,ny,m;
bool find(int u)
{
for(int i = 0; i < ny; i++){
if(G[u][i] && !vis[i]){
vis[i] = 1;
if(match[i] == -1 || find(match[i])){
match[i] = u;
return true;
}
}
}
return false;
}
int max_match()
{
int ans = 0;
memset(match,-1,sizeof(match));
for(int i = 0; i < nx; i++){
memset(vis,0,sizeof(vis));
if(find(i)){
ans++;
}
}
return ans;
}
int main(void)
{
while(scanf("%d",&nx) != EOF && nx){
scanf("%d%d",&ny,&m);
memset(G,0,sizeof(G));
for(int i = 1; i <= m; i++){
int id,x,y;
scanf("%d%d%d",&id,&x,&y);
if(x && y){
G[x][y] = 1;
}
}
printf("%d\n",max_match());
}
return 0;
}
给出一个有向图,士兵沿着路的方向走,问至少需要多少士兵,能把所有边都走一遍。其实就是求有向无环图的最小路径覆盖,有向无环图的最小路径覆盖=顶点数-最大匹配数
#include <stdio.h>
#include <string.h>
const int maxn = 150;
int match[maxn],G[maxn][maxn],vis[maxn];
int n,m;
bool find(int u)
{
for(int i = 1; i <= n; i++){
if(G[u][i] && !vis[i]){
vis[i] = 1;
if(match[i] == -1 || find(match[i])){
match[i] = u;
return true;
}
}
}
return false;
}
int max_match()
{
int ans = 0;
memset(match,-1,sizeof(match));
for(int i = 1; i <= n; i++){
memset(vis,0,sizeof(vis));
if(find(i)){
ans++;
}
}
return ans;
}
int main(void)
{
int T;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
memset(G,0,sizeof(G));
for(int i = 1; i <= m; i++){
int a,b;
scanf("%d%d",&a,&b);
G[a][b] = 1;
}
printf("%d\n",n - max_match());
}
return 0;
}
找出一个最大的集合,使这个集合的任意两个同学都没有浪漫的关系。求的是最大独立集(在n个点的图中,选出一个最大点集m,并且这m个点任意两点之间都没有边相连),最大独立集=顶点数-最大匹配数,这道题并没有指定性别,所以要减去最大匹配/2。
#include <stdio.h>
#include <string.h>
const int maxn = 1005;
int match[maxn],G[maxn][maxn],vis[maxn];
int n,m;
bool find(int u)
{
for(int i = 0; i < n; i++){
if(G[u][i] && !vis[i]){
vis[i] = 1;
if(match[i] == -1 || find(match[i])){
match[i] = u;
return true;
}
}
}
return false;
}
int max_match()
{
int ans = 0;
memset(match,-1,sizeof(match));
for(int i = 0; i < n; i++){
memset(vis,0,sizeof(vis));
if(find(i)){
ans++;
}
}
return ans;
}
int main(void)
{
while(scanf("%d",&n) != EOF){
memset(G,0,sizeof(G));
int id,num;
for(int i = 0; i < n; i++){
scanf("%d: (%d)",&id,&num);
while(num--){
scanf("%d",&id);
G[i][id] = 1;
}
}
printf("%d\n",n - max_match() / 2);
}
return 0;
}
对于一个点(i,j),如果放上车,就在i,j之间连上一条边,放的车的最大个数就是最大匹配数,先求出最大匹配数,然后枚举每一个点,求出去掉这个点之后的最大匹配数,如果比之前的少,说明是重要点。
#include <stdio.h>
#include <string.h>
int match[105];
int x[10005],y[10005];
int n,m,k;
int vis[105];
int a[105][105];
int b[105][105];
int find(int u){
for(int i=1;i<=m;i++){
if(vis[i] == 0 && a[u][i] && !b[u][i]){
vis[i] = 1;
if(match[i] == 0 || find(match[i])){
match[i] = u;
return 1;
}
}
}
return 0;
}
int main(void){
int count = 0;
while(scanf("%d%d%d",&n,&m,&k)!=EOF){
memset(match,0,sizeof(match));
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
for(int i=1;i<=k;i++){
scanf("%d%d",&x[i],&y[i]);
a[x[i]][y[i]] = 1;
}
int ans=0;
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
if(find(i)){
ans++;
}
}
int anss = 0;
for(int i=1;i<=k;i++){
b[x[i]][y[i]] = 1;
memset(match,0,sizeof(match));
int temp=0;
for(int j=1;j<=n;j++){
memset(vis,0,sizeof(vis));
if(find(j)){
temp++;
}
}
if(temp < ans){
anss++;
}
b[x[i]][y[i]] = 0;
}
printf("Board %d have %d important blanks for %d chessmen.\n",++count,anss,ans);
}
return 0;
}
给出一个矩阵,每一个数代表一种气球,求有多少种气球,每次刷掉一行或一列,在最多刷k次的情况下刷不完,按字典序输出,不存在则输出-1。还是根据行列坐标建图,并且图里存储的是哪种气球,选出最少的行号或列号覆盖所有储存这种颜色的所有边,最小点覆盖集,即求最大匹配,然后与k比较。
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int maxn = 105;
int ans[maxn];
int match[maxn],G[maxn][maxn],vis[maxn],used[maxn];
int n,c,k;
bool find(int u)
{
for(int i = 1; i <= n; i++){
if(G[u][i] == c && !vis[i]){
vis[i] = 1;
if(match[i] == -1 || find(match[i])){
match[i] = u;
return true;
}
}
}
return false;
}
int max_match()
{
int ans = 0;
memset(match,-1,sizeof(match));
for(int i = 1; i <= n; i++){
memset(vis,0,sizeof(vis));
if(find(i)){
ans++;
}
}
return ans;
}
int main(void)
{
while(scanf("%d%d",&n,&k) != EOF){
if(n == 0 && k == 0){
break;
}
memset(G,0,sizeof(G));
memset(used,0,sizeof(used));
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
scanf("%d",&G[i][j]);
}
}
int cnt = 0;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
if(!used[G[i][j]]){
used[G[i][j]] = 1;
c = G[i][j];
int num = max_match();
if(num > k){
ans[cnt++] = c;
}
}
}
}
if(cnt == 0){
printf("-1\n");
}
else{
sort(ans,ans + cnt);
for(int i = 0; i < cnt; i++){
if(i == cnt - 1){
printf("%d\n",ans[i]);
}
else{
printf("%d ",ans[i]);
}
}
}
}
return 0;
}
很明显的求最大匹配,就是需要判断一下两张牌的大小关系。
#include <stdio.h>
#include <string.h>
#include <map>
using namespace std;
char a[50][2],b[50][2];
int match[50],vis[50];
int n;
map <char,int> mp;
void init()
{
for(char i = '2'; i <= '9'; i++){
mp[i] = i - '0';
}
mp['T'] = 10;
mp['J'] = 11;
mp['Q'] = 12;
mp['K'] = 13;
mp['A'] = 14;
mp['H'] = 3;
mp['S'] = 2;
mp['D'] = 1;
mp['C'] = 0;
}
bool cmp(int i,int j)
{
int ansb = mp[b[i][0]];
int ansa = mp[a[j][0]];
if(ansa == ansb){
ansb += mp[b[i][1]];
ansa += mp[a[j][1]];
}
else{
return ansb > ansa;
}
return ansb > ansa;
}
bool find(int u)
{
for(int i = 1; i <= n; i++){
if(cmp(u,i) && !vis[i]){
vis[i] = 1;
if(match[i] == -1 || find(match[i])){
match[i] = u;
return true;
}
}
}
return false;
}
int max_match()
{
int ans = 0;
memset(match,-1,sizeof(match));
for(int i = 1; i <= n; i++){
memset(vis,0,sizeof(vis));
if(find(i)){
ans++;
}
}
return ans++;
}
int main(void)
{
int T;
init();
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i = 1; i <= n; i++){
scanf("%s",a[i]);
}
for(int i = 1; i <= n; i++){
scanf("%s",b[i]);
}
printf("%d\n",max_match());
}
return 0;
}
这道题建出二分图是关键,先给每个空白点编号,然后横纵坐标相加为奇数的空白点为X集合,横纵坐标相加为偶数的的空白点为Y集合,如果两个点,一个属于X,一个属于Y,并且这两个点上或下或左或右相邻,两个点连接一条边,这样就构建除了二分图,求最大匹配即可,如何打印出结果看代码。
#include <stdio.h>
#include <string.h>
const int maxn = 105;
int G[maxn][maxn],vis[maxn];
int bin[maxn][maxn],match[maxn];
int num[maxn][maxn];
int node[maxn][2];
int n,m;
int to[4][2] = {0,1,1,0,-1,0,0,-1};
int cnt;
bool find(int u)
{
for(int i = 1; i < cnt; i++){
if(bin[u][i] && !vis[i]){
vis[i] = 1;
if(match[i] == -1 || find(match[i])){
match[i] = u;
return true;
}
}
}
return false;
}
int max_match()
{
int ans = 0;
memset(match,-1,sizeof(match));
for(int i = 1; i < cnt; i++){
memset(vis,0,sizeof(vis));
if(find(i)){
ans++;
}
}
return ans;
}
int main(void)
{
while(scanf("%d%d",&n,&m) != EOF){
if(n + m == 0){
break;
}
int k;
scanf("%d",&k);
memset(G,0,sizeof(G));
memset(bin,0,sizeof(bin));
memset(node,0,sizeof(node));
memset(num,0,sizeof(num));
for(int i = 1; i <= k; i++){
int a,b;
scanf("%d%d",&a,&b);
G[a][b] = 1;
}
cnt = 1;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if(!G[i][j]){
node[cnt][0] = i;
node[cnt][1] = j;
num[i][j] = cnt++;
}
}
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if((i + j) & 1 && num[i][j]){
for(int q = 0; q <= 3; q++){
int x = i + to[q][0];
int y = j + to[q][1];
if(x >= 1 && y >= 1 && x <= n && y <= m && num[x][y]){
//printf("%d___%d\n",num[i][j],num[x][y]);
bin[num[i][j]][num[x][y]] = 1;
}
}
}
}
}
printf("%d\n",max_match());
for(int i = 1; i < cnt; i++){
if(match[i] != -1){
printf("(%d,%d)--(%d,%d)\n",node[i][0],node[i][1],node[match[i]][0],node[match[i]][1]);
}
}
printf("\n");
}
return 0;
}
因为*同时消毒两次,所以尽量用*,将所有操作解码,用一个vis数组标记,对于二进制只有一位不同的两个点连接一条边,求最大独立集,注意这个图是有向图,因为两个只差一位的数合并成一个,建图时是遍历所有的点,一个点用完后和它连接的点会再次出现,而我们要求只匹配一次即可。
#include <stdio.h>
#include <string.h>
#include <vector>
using namespace std;
const int maxn = 1 << 11;
int vis[maxn],used[maxn];
int match[maxn],n,m,cnt;
vector<int> G[maxn];
void input(){
for(int i = 0; i < (1 << n); i++){
G[i].clear();
vis[i] = 0;
}
int flag;
char op[15];
int val;
for(int i = 0; i < m; i++){
flag = -1;
val = 0;
scanf("%s",op);
for(int j = 0; j < n; j++){
if(op[j] == '*'){
flag = j;
}
else if(op[j] == '1'){
val |= (1 << j);
}
}
vis[val] = 1;
if(flag != -1){
val |= (1 << flag);
vis[val] = 1;
}
}
cnt = 0;
for(int i = 0; i < (1 << n); i++){
if(vis[i]){
cnt++;
for(int j = 0; j < (1 << n); j++){
if(vis[j]){
int temp = i ^ j;
if(temp && !((temp) & (temp - 1))){
G[i].push_back(j);
G[j].push_back(i);
}
}
}
}
}
}
bool find(int i){
for(int j = 0; j < G[i].size(); j++){
int v = G[i][j];
if(!used[v]){
used[v] = 1;
if(match[v] == -1 || find(match[v])){
match[v] = i;
return true;
}
}
}
return false;
}
int max_match(){
memset(match,-1,sizeof(match));
int ans = 0;
for(int i = 0; i < (1 << n); i++){
if(vis[i]){
memset(used,0,sizeof(used));
if(find(i)){
ans++;
}
}
}
return ans;
}
int main(void){
while(scanf("%d%d",&n,&m) != EOF && (n + m)){
input();
printf("%d\n",cnt - max_match() / 2);
}
return 0;
}
先对任意两条街道求最短路,用floyd就行,然后建图,两个任务i,j,如果完成i之后在到达j时的时间小于最晚到达j的时间,则i与j之间加一条边,求最小路径覆盖即可。
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
int dis[25][25];
int G[205][205],match[205],vis[205];
int p[205],t[205],d[205];
int Q,M;
void floyd()
{
for(int k = 1; k <= Q; k++){
for(int i = 1; i <= Q; i++){
for(int j = 1; j <= Q; j++){
if(dis[i][j] > dis[i][k] + dis[k][j]){
dis[i][j] = dis[i][k] + dis[k][j];
}
}
}
}
}
void input()
{
for(int i = 1; i <= Q; i++){
for(int j = 1; j <= Q; j++){
scanf("%d",&dis[i][j]);
if(dis[i][j] == -1){
dis[i][j] = INF;
}
}
}
for(int i = 1; i <= M; i++){
scanf("%d%d%d",&p[i],&t[i],&d[i]);
}
}
void get_map()
{
for(int i = 1; i <= M; i++){
for(int j = 1; j <= M; j++){
if(t[i] + dis[p[i]][p[j]] + d[i] <= t[j]){
G[i][j] = 1;
}
}
}
}
void init()
{
for(int i = 1; i <= M; i++){
for(int j = 1; j <= M; j++){
G[i][j] = 0;
}
}
}
bool find(int u)
{
for(int i = 1; i <= M; i++){
if(G[u][i] && !vis[i]){
vis[i] = 1;
if(match[i] == -1 || find(match[i])){
match[i] = u;
return true;
}
}
}
return false;
}
int max_match()
{
int ans = 0;
memset(match,-1,sizeof(match));
for(int i = 1; i <= M; i++){
memset(vis,0,sizeof(vis));
if(find(i)){
ans++;
}
}
return ans;
}
int main(void)
{
while(scanf("%d%d",&Q,&M) && (Q + M)){
init();
input();
floyd();
get_map();
printf("%d\n",M - max_match());
}
return 0;
}