Purifying Machine UVA - 1663 (二分图最大匹配)

传送门

题意:给m个长度为n的模板串,每个模板串包含字符0,1和最多一个星号“*“,其中星号可以匹配0或1。改写这个模板集合,使得模板的个数最少。也就是一个*可以匹配两个串

题解:两两判断,如果二者的二进制只有一位不同,在s1和s2之间连一条边,对应一个带"*”的模板串,其中“*”的位置就是s1和s2不同的那一位,而s1和s2中1的个数的奇偶性必然不同,可以按照这个性质将所有点分为两类,就形成了一个二分图。这个二分图的每一个匹配都对应一个带"*"的模板集合。求此二分图的最大匹配,假设其中有n条边,则所求的模板集合的最小值就是|S|-n.

附上代码:


#include<bits/stdc++.h>
#define _for(i,a,b) for( int i=(a); i<(b); ++i)
#define _rep(i,a,b) for( int i=(a); i<=(b); ++i)
using namespace std;

int countBit(int x, int w){
    int b = 1, ans = 0;
    _for(i, 0, w) ans += ((b&x)!=0), b <<= 1;
    return ans;
}

string printbin(int x, int w){
    string buf;
    for(int i = w-1; i >= 0; i--) buf += (((1<<i)&x) > 0 ? 1 : 0) + '0';
    return buf;
}

// 二分图最大基数匹配
template<int maxn>
struct BPM {
  int n, m;               // 左右顶点个数
  vector<int> G[maxn];    // 邻接表
  int left[maxn];         // left[i]为右边第i个点的匹配点编号,-1表示不存在
  bool T[maxn];           // T[i]为右边第i个点是否已标记

  int right[maxn];        // 求最小覆盖用
  bool S[maxn];           // 求最小覆盖用

  void init(int n, int m) {
    this->n = n;
    this->m = m;
    for(int i = 0; i < n; i++) G[i].clear();
  }

  void AddEdge(int u, int v) {
    G[u].push_back(v);
  }

  bool match(int u){
    S[u] = true;
    for(int i = 0; i < G[u].size(); i++) {
      int v = G[u][i];
      if (!T[v]){
        T[v] = true;
        if (left[v] == -1 || match(left[v])){
          left[v] = u;
          right[u] = v;
          return true;
        }
      }
    }
    return false;
  }

  // 求最大匹配
  int solve() {
    fill_n(left, maxn, -1);
    fill_n(right, maxn, -1);
    int ans = 0;
    for(int u = 0; u < n; u++) { // 从左边结点u开始增广
      fill_n(S, sizeof(S), 0);
      fill_n(T, sizeof(T), 0);
      if(match(u)) ans++;
    }
    return ans;
  }

  // 求最小覆盖。X和Y为最小覆盖中的点集
  int mincover(vector<int>& X, vector<int>& Y) {
    int ans = solve();
    fill_n(S, maxn, 0);
    fill_n(T, maxn, 0);
    for(int u = 0; u < n; u++)
      if(right[u] == -1) match(u); // 从所有X未盖点出发增广
    for(int u = 0; u < n; u++)
      if(!S[u]) X.push_back(u); // X中的未标记点
    for(int v = 0; v < m; v++)
      if(T[v]) Y.push_back(v);  // Y中的已标记点
   return ans;
  }
};

const int MAXM = 1024 + 5;
BPM<MAXM> solver;
string S[MAXM];
int N, M, Set[MAXM];
int main(){
    string buf;
    set<int> vs; // verticles
    while(true){
        cin>>N>>M;
        if(N == 0) break;
        int sz = 1<<N;
        fill_n(Set, sz, 0);
        solver.init(sz, sz);
        _for(i, 0, M){
            cin>>buf;
            int x = -1, v = 0, bit = 1;
            _for(j, 0, buf.size()) {
                char c = buf[j];
                if(c == '*') x = j, bit = 1;
                else bit = c - '0';
                v = v*2 + bit;
            }
            Set[v] = 1;
            if(x != -1) {
                v &= ~(1<<(N-x-1));
                Set[v] = 1;
            }
        }
        
        // 左边是偶数个1的串,右边是奇数个1的模板串
        int cnt = 0;
        _for(i, 0, sz) {
            if(!Set[i]) continue;
            // cout<<printbin(i, N)<<", ";
            cnt++;
            if(countBit(i, N)%2 == 1) continue;
            _for(b, 0, N){
                int j = (1<<b)^i;
                if(Set[j]) solver.AddEdge(i, j);
            }
        }
        
        int m = solver.solve();
        cout<<cnt-solver.solve()<<endl;
    }
    
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zhouzi2018/article/details/85255382
今日推荐