挑战NPC 一般图最大匹配

题意:n个球,放进m个盒子,每个盒子最多放三个球,每个球可以放的盒子是一个集合,半空的盒子是指盒子内的球个数<= 1的盒子。求半空的盒子数目的最大值。

一般图最大匹配。
把每个盒子分成三个,三个间两两连边,每个点向他所能到的盒子的三个分点连边,跑最大匹配,最后的答案就是,maxmatch - n。
这个证明比较容易,但是非常难想,我还需要研究一下这之间的思维过程。

学了一下带花树算法,可能一知半解。
大致思路就是,我们增广的时候,把奇环缩成一个点然后当二分图做,实现的过程利用BFS,寻找奇环,缩点并且扩展新的点。 思路是这样,实现时用并查集实现。

#include <bits/stdc++.h>
using namespace std;

const int N = 666;

int n , m , x , y , mat[N] , pre[N] , fa[N] , ty[N] , e , n2;
bool way[N][N] , inq[N];

void add(int x , int y) {
  way[x][y] = way[y][x] = 1;
}

int getf(int x) {
  return (fa[x] == x) ? x : fa[x] = getf(fa[x]);
}

void init(void) {
  for(int i = 1;i <= n2;++ i) fa[i] = i , pre[i] = ty[i] = 0;
}

bool vis[N];

int LCA(int x , int y) {
  memset(vis , 0 , sizeof(vis));
  for(int i = x;i;i = pre[mat[i]]) i = getf(i) , vis[i] = 1;
  for(int i = y;i;i = pre[mat[i]]) {i = getf(i); if(vis[i]) return i;}
}

queue <int> q;

void shrink(int x , int y , int lca) {
  while(getf(x) != lca) {
    pre[x] = y; y = mat[x];
    if(ty[y] == 2) q.push(y); ty[y] = 1;
    fa[x] = fa[y] = lca;
    x = pre[y];
  }
}

int aug(int x) {
  memset(inq , 0 , sizeof(inq)); init();
  while(!q.empty()) q.pop();
  q.push(x); inq[x] = 1; ty[x] = 1;
  while(!q.empty()) {
    int u  = q.front(); q.pop();
    for(int i = 1;i <= n2;++ i) if(way[u][i]) {
    if(ty[i] == 2 || getf(i) == getf(u)) continue;
    if(!ty[i]) {
      pre[i] = u; ty[i] = 2;
      if(!mat[i]) {
        for(int a = i , la , b;a;a = la) {
          la = pre[a];b = mat[la]; mat[a] = la; mat[la] = a;la = b;
        }
        return 1;
      }
      ty[mat[i]] = 1; q.push(mat[i]); inq[mat[i]] = 1;
    }
    else {
      int lca = LCA(u , i);
      shrink(u , i , lca); shrink(i , u , lca);
    }
      }
  }
  return 0;
}

void onedance(void) {
  memset(mat , 0 , sizeof(mat)); memset(way , 0 , sizeof(way));
  int ans = 0;
  scanf("%d%d%d" , &n , &m , &e);
  n2 = n + 3 * m;
  for(int i = 1;i <= e;++ i) {
    scanf("%d%d" , &x , &y);
    for(int j = 1;j <= 3;++ j) add(x , n + 3 * y - 3 + j);
  }
  for(int i = 1;i <= m;++ i) {
    for(int j = 1;j <= 3;++ j) {
      for(int k = j + 1;k <= 3;++ k) add(n + 3 * i - 3 + j , n + 3 * i - 3 + k);
    }
  }
  for(int i = 1;i <= n2;++ i) if(!mat[i]) ans += aug(i);
  cout << ans - n << endl;
  for(int i = 1;i <= n;++ i) {
    printf("%d " , (mat[i] - n + 3 - 1) / 3);
  }
  puts("");
}

main(void) {
  int t;
  for(scanf("%d" , &t);t --;) {
    onedance();
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值