二分圖(例題)

无敌の暗黑魔王發表於2024-05-12
    https://www.cnblogs.com/kuangbiaopilihu/p/18184536

$\quad $ 這裡不再介紹二分圖的基礎知識,只是一些例題的解釋。

$\quad $ 當然,這道題可以用二分+並查集來解決。但這是二分圖專輯,所以介紹一下二分圖做法。
$\quad $ 首先如果兩個罪犯之間有仇恨,那麼當他們不在同一所監獄時不會發生衝突。若要若干個罪犯之間不產生衝突,那麼將有仇恨的罪犯連邊,則不會發生衝突的罪犯恰好形成一個二分圖。
$\quad $ 所以按照有仇恨罪犯之間的怒氣值排序,再二分一下答案下標,把邊權大於二分答案的邊加進去,如果形成了一個二分圖,則答案合法。然後便可得出答案。

點選檢視程式碼
  #include<bits/stdc++.h>
  using namespace std;
  const int N=1e5+100;
  vector<int> sl[N];
  struct stu{
      int x,y,w;
  }s[N];
  int col[N],n,m,ans;
  bool is_gragh(int cur,int fa,int color){
      col[cur]=color;
      for(int i=0;i<sl[cur].size();i++){
          int y=sl[cur][i];
          if(col[y]==color)return false;
          if(col[y]==0&&!is_gragh(y,cur,3-color))return false;
      }
      return true;
  }
  bool check(int x){
      for(int i=1;i<=n;i++)sl[i].clear();
      memset(col,0,sizeof col);
      for(int i=x+1;i<=m;i++){
          int x=s[i].x,y=s[i].y;
          sl[x].push_back(y);
          sl[y].push_back(x);
      }
      for(int i=1;i<=n;i++)if(col[i]==0)if(!is_gragh(i,0,1))return false;
      return true;
  }
  bool cmp(stu a,stu b){return a.w<b.w;}
  int main(){
      scanf("%d%d",&n,&m);
      for(int i=1;i<=m;i++)scanf("%d%d%d",&s[i].x,&s[i].y,&s[i].w);
      sort(s+1,s+1+m,cmp);
      int l=0,r=m;
      while(l<=r){
          int mid=(l+r)>>1;
          if(check(mid))r=mid-1,ans=mid;
          else l=mid+1;
      }
      printf("%d",s[ans].w);
      return 0;
  }

$\quad $ 可以發現,如果選擇了一列,那麼處於這一列的點將都被消除,那麼就可以將該點與其所在行與所在列相連,以表示其關聯。先拿樣例舉例:

$\quad $ 我們發現,點只存在於行和列之間的邊上,那麼將點省去,可以得到一個二分圖。這樣問題就變為了一個二分圖的點最大覆蓋問題,求最大匹配即可。

點選檢視程式碼


  #include<bits/stdc++.h>
  using namespace std;
  const int N=1e4+100;
  bool vis[N];
  int n,k,match[N];
  vector<int> s[N<<1];
  bool dfs(int x){
      for(int i=0;i<s[x].size();i++){
          int y=s[x][i];
          if(!vis[y]){
              vis[y]=1;
              if(!match[y]||dfs(match[y])){
                  match[y]=x;
                  return true;
              }
          }
      }
      return false;
  }
  int Hungary(){
      int ans=0;
      for(int i=1;i<=n;i++){
          memset(vis,0,sizeof vis);
          if(dfs(i))ans++;
      }
      return ans;
  }
  int main(){
      scanf("%d%d",&n,&k);
      n<<=1;
      for(int i=1;i<=k;i++){
          int x,y;
          scanf("%d%d",&x,&y);
          y+=n;
          s[x].push_back(y);
          s[y].push_back(x);
      }
      printf("%d",Hungary());
      return 0;
  }

$\quad $ 還是先膜樣例,這裡用漢字表示錦囊,阿拉伯數字表示題目。

$\quad $ 同樣可以得到一張二分圖,只不過這道題不是要求最大匹配,因為答題出現錯誤就淘汰了,仔細觀察匈牙利演算法程式碼,可以發現他正是從1順序開始尋找的,所以我們只要在無法匹配時打斷迴圈即可。

點選檢視程式碼


  #include<bits/stdc++.h>
  using namespace std;
  const int N=1e4+100;
  bool vis[N];
  int n,k,match[N];
  vector<int> s[N<<1];
  bool dfs(int x){
      for(int i=0;i<s[x].size();i++){
          int y=s[x][i];
          if(!vis[y]){
              vis[y]=1;
              if(!match[y]||dfs(match[y])){
                  match[y]=x;
                  return true;
              }
          }
      }
      return false;
  }
  int Hungary(){
      int ans=0;
      for(int i=1;i<=n;i++){
          memset(vis,0,sizeof vis);
          if(dfs(i))ans++;
          else break;
      }
      return ans;
  }
  int main(){
      scanf("%d%d",&n,&k);
      for(int i=1;i<=k;i++){
          int x,y;
          scanf("%d%d",&x,&y);
          x++,y++;
          s[i].push_back(y);
          s[i].push_back(x);
      }
      printf("%d",Hungary());
      return 0;
  }

$\quad $ 先求出所有小衫到所有出口所需時間,對時間小於k的情況,就將兩者相連,最後還是的到一張二分圖,此時只需要求出最大匹配即可。
注意開double!!

點選檢視程式碼
  #include<bits/stdc++.h>
  using namespace std;
  const int N=1e4+100;
  bool vis[N];
  int n,k,match[N],m;
  vector<int> s[N<<1];
  double x[N],y[N];
  bool dfs(int x){
      for(int i=0;i<s[x].size();i++){
          int y=s[x][i];
          if(!vis[y]){
              vis[y]=1;
              if(!match[y]||dfs(match[y])){
                  match[y]=x;
                  return true;
              }
          }
      }
      return false;
  }
  int Hungary(){
      int ans=0;
      for(int i=1;i<=m;i++){
          memset(vis,0,sizeof vis);
          if(dfs(i))ans++;
      }
      return ans;
  }
  int main(){
      scanf("%d%d%d",&m,&n,&k);
      //m是小衫個數,n是點數,k是邊權最大值。
      for(int i=1;i<=n;i++)scanf("%lf%lf",&x[i],&y[i]);
      for(int i=1;i<=m;i++){
          double xl,yl,vl,tl;
          scanf("%lf%lf%lf",&xl,&yl,&vl);
          for(int j=1;j<=n;j++){
              tl=sqrt((x[j]-xl)*(x[j]-xl)+(y[j]-yl)*(y[j]-yl));
              tl/=vl;
              // cout<<tl<<endl;
              if(k>=tl)s[i].push_back(j+m),s[j+m].push_back(i);
          }
      }
      printf("%d",Hungary());
      return 0;
  }

$\quad $ 這道題和穿越小行星群很像,但是有石頭阻攔,對於有石頭阻攔的,我們可以將一行視為兩行、一列視為兩列,再將合法的位置與其行列連邊。這樣又得到一張二分圖,再求最大匹配即可。

點選檢視程式碼
#inclu  de<bits/stdc++.h>
  using namespace std;
  const int N=65;
  char ch[N*N][N*N];
  bool vis[N*N];
  int n,m,match[N*N],row[N*N][N*N],col[N*N][N*N];
  int ntot,ltot;
  vector<int>s[N*N];
  bool dfs(int x){
      for(int i=0;i<s[x].size();i++){
          int y=s[x][i];
          if(!vis[y]){
              vis[y]=1;
              if(!match[y]||dfs(match[y])){
                  match[y]=x;
                  return true;
              }
          }
      }
      return false;
  }
  int Hungary(){
      int ans=0;
      for(int i=1;i<=ntot;i++){
          memset(vis,0,sizeof vis);
          if(dfs(i))ans++;
      }
      return ans;
  }
  int main(){
      scanf("%d%d",&m,&n);
      for(int i=1;i<=m;i++)scanf("%s",ch[i]+1);
      for(int i=1;i<=m;i++){
          for(int j=1;j<=n;j++){
              if(ch[i][j]-'#'){
                  if(j>1&&ch[i][j-1]-'#')row[i][j]=row[i][j-1];
                  else row[i][j]=++ntot;
                  if(i>1&&ch[i-1][j]-'#')col[i][j]=col[i-1][j];
                  else col[i][j]=++ltot;
              }

          }
      }
      for(int i=1;i<=m;i++){
          for(int j=1;j<=n;j++){
              if(ch[i][j]=='o')s[row[i][j]].push_back(col[i][j]+ntot);
          }
      }
      printf("%d",Hungary());
  }

相關文章