D179 二分图最大独立集 匈牙利算法 CF1404E Bricks

D179 二分图最大独立集 匈牙利算法 CF1404E Bricks_哔哩哔哩_bilibili

 

CF1404E Bricks - 洛谷        Problem - 1404E - Codeforces

用一些 1×𝑥 的砖精确覆盖一个 𝑛×𝑚 的网格中的所有黑色格子,砖横放竖放都行,但不能重叠,问最少需要多少块砖。

 

在二分图中,选出最多的点,使得选出的点之间没有边,这些点构成二分图最大独立集

结论:在二分图中,① 最小点覆盖 $=$ 最大匹配数    ② 最大独立集 $+$ 最小点覆盖 $=$ 总点数

思路

考虑先在所有能覆盖的网格上全部铺上 1×1 的砖

对于一个 1×𝑥 的砖,可以由同一行的 𝑥 个连续的 1×1 砖依次“行合并”

对于一个 𝑥×1 的砖,可以由同一列的 𝑥 个连续的 1×1 砖依次“列合并”。显然,合并的次数越多,砖块数量越少

合并相当于“消除”格边,格边分两类:横格边、竖格边,把横格边转化为红点,把竖格边转化为蓝点

选择红点对应“列合并”,选择蓝点对应“行合并”,显然,相邻的红点和蓝点不能同时选,否则砖块重叠

把红点作为左部点,蓝点作为右部点,把相邻的红点和蓝点之间连边,表示边的两个端点不能同时选

要想合并最多的次数,就要选最多的独立的红蓝点,即二分图的最大独立集。图中最大独立集为 5 个红点 + 1 个蓝点

最终答案 $=$ 黑格总数 $-$ 红蓝点的最大独立集 $=$ 黑格总数 $-\;($红蓝点数量 $-$ 红蓝点的最大匹配数$)$

红蓝点数为 $O(N^2)$,连边数为 $O(N^2)$,时间复杂度为 $O(N^4)$,匈牙利算法要加优化

参考:D178 二分图最小点覆盖 匈牙利算法 P6062 [USACO05JAN] Muddy Fields G - 董晓 - 博客园

     D172 二分图最大匹配 匈牙利算法 P5182 棋盘覆盖 - 董晓 - 博客园

 

// 二分图 匈牙利算法 O(V*E) 优化:去掉 memset
#include<bits/stdc++.h>
using namespace std;

const int N=205,M=N*N;
string s[N];
int n,m,black,cnt,sx,sy,x[N][N],y[N][N];
vector<int> e[M];
int match[M];
bitset<M> vis; //各二进制位清空很快

bool dfs(int u){
  for(auto v:e[u]){
    if(vis[v]) continue; //若女生v已参与当前轮试配,就跳过
    vis[v]=1; //记录女生v参与当前轮试配
    if(!match[v] || dfs(match[v])){ //若女生v没配对 或 女生v的男友可以另选她人
      match[v]=u; //记录v的男友是u
      return 1;   //返回配对成功
    }
  }
  return 0; //返回配对失败
}
int main(){
  ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
  cin>>n>>m;
  for(int i=1;i<=n;i++) cin>>s[i],s[i]=" "+s[i]; //网格字符
  for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(s[i][j]=='#'){ //黑格尔
    black++;
    if(i<n&&s[i+1][j]=='#') x[i][j]=++sx; //(i,j)下的横格边转化为红点
    if(j<m&&s[i][j+1]=='#') y[i][j]=++sy; //(i,j)右的竖格边转化为绿点
  }
  for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(s[i][j]=='#'){ //黑格尔
    if(s[i+1][j]=='#'){
      if(s[i][j-1]=='#') e[x[i][j]].push_back(y[i][j-1]); //红点→左上绿点
      if(s[i][j+1]=='#') e[x[i][j]].push_back(y[i][j]);   //红点→右上绿点
      if(s[i+1][j-1]=='#') e[x[i][j]].push_back(y[i+1][j-1]); //红点→左下绿点
      if(s[i+1][j+1]=='#') e[x[i][j]].push_back(y[i+1][j]);   //红点→右下绿点      
    }
  }
  
  for(int i=1;i<=sx;i++){ //枚举左部点(红点)
    vis=0; 
    cnt+=dfs(i); //累加最大匹配数
  }
  
  cout<<black-(sx+sy-cnt); //黑格总数-(红点数+绿点数-最大匹配数)
}

 

posted @ 2026-05-31 18:50  董晓  阅读(23)  评论(0)    收藏  举报