二分图染色 笔记

二分图染色 笔记

大致思路

把二分图中不能处于同一集合的点连一条边,在该边两端染上不同颜色,即 01 。若出现矛盾则说明不能成立,直接返回。

例题1——封锁阳光大学

https://www.luogu.com.cn/problem/P1330

题目描述

曹是一只爱刷街的老曹,暑假期间,他每天都欢快地在阳光大学的校园里刷街。河蟹看到欢快的曹,感到不爽。河蟹决定封锁阳光大学,不让曹刷街。

阳光大学的校园是一张由 \(n\) 个点构成的无向图,\(n\) 个点之间由 \(m\) 条道路连接。每只河蟹可以对一个点进行封锁,当某个点被封锁后,与这个点相连的道路就被封锁了,曹就无法在这些道路上刷街了。非常悲剧的一点是,河蟹是一种不和谐的生物,当两只河蟹封锁了相邻的两个点时,他们会发生冲突。

询问:最少需要多少只河蟹,可以封锁所有道路并且不发生冲突。

输入格式

第一行两个正整数,表示节点数和边数。
接下来 \(m\) 行,每行两个整数 \(u,v\),表示点 \(u\) 到点 \(v\) 之间有道路相连。

输出格式

仅一行如果河蟹无法封锁所有道路,则输出 Impossible,否则输出一个整数,表示最少需要多少只河蟹。

样例 #1

样例输入 #1

3 3
1 2
1 3
2 3

样例输出 #1

Impossible

样例 #2

样例输入 #2

3 2
1 2
2 3

样例输出 #2

1

提示

【数据规模】
对于 \(100\%\) 的数据,\(1\le n \le 10^4\)\(1\le m \le 10^5\),保证没有重边。


题意

题意可以转化为对于每一个边,都严格地有且仅有一个点被选中。

思路

每个边的两个点不能出现在同一集合,即可转化为二分图染色问题。

code

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
vector<int>g[N];
int col[N],n,m,s[2];
bool dfs(int u,int c)
{
	col[u]=c;s[c]++;
	for(auto v:g[u]) 
	{
		if(col[v]==col[u]) return false;
		if(col[v]==-1&&!dfs(v,!c)) return false;
	}
	return true;
}

int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	memset(col,-1,sizeof(col));
	int ans=0;
	for(int i=1;i<=n;i++) 
	{
		int pre0=s[0],pre1=s[1];
		if(col[i]==-1&&!dfs(i,0))
		{
			cout<<"Impossible"<<endl;
			return 0;
		}
		ans+=min(s[0]-pre0,s[1]-pre1);
	} 
	cout<<ans<<endl;
	return 0;
}

例题2——西湖有雅座

https://www.luogu.com.cn/problem/P11409

题目背景

江湖路上走走停停,翻开年少漂泊的回忆。
如今走过这世间,万般留恋,风吹起了从前 。

题目描述

孙亦谐准备建西湖雅座来开饭馆。

他有 \(n\) 个零件,零件的大小均为 \(h\times w\)。零件从 \(1 \sim n\) 编号。

对于一个大小为 \(h \times w\) 的零件,其可视为一个 \(h\)\(w\) 列的矩阵 \(w\)。若用 \(a_{i,j}\) 来表示这个矩阵中第 \(i\) 行第 \(j\) 列的元素。对于 \(\forall a_{i,j}\),都有 $a_{i,j}\in \left { 0,1\right } $。

则编号为 \(i\) 的零件的面积为: $S\left(i\right) = \sum_{i = 1}^{h}\sum_{j = 1}^{w}a_{i,j} $。

若编号为 \(l\)\(r\) 的两个零件的分别表示为矩阵 \(a\)\(b\),其在同一座楼的稳固程度可表示为:

\[f\left(l,r\right) = \sum_{i = 1}^{h} \sum_{j = 1}^{w} \left [ (a_{i,j} = 1) \text{ and } (b_{i,j} = 1) \right ] \]

孙亦谐需要将这 \(n\) 个零件先选取若干个按照任意顺序排列搭成大楼,然后把剩余的零件搭成小楼。若没有剩余零件,则可以不搭小楼。

\(U\) 表示某座楼选取的零件编号的集合,则这座楼能成功搭建的条件是:

\[\forall i,j \in U,f\left (i,j\right) \ge \lceil \frac{ \min \left ( S\left(i \right),S\left(j\right) \right) }{2}\rceil \]

孙亦谐想知道在保证两座楼能成功搭建的条件下,让大楼使用的零件数尽量多。若无法成功搭建,则直接输出 -1

输入格式

第一行输入包含三个正整数 \(n,h,w\),分别表示零件的个数、零件的行数、零件的列数。

接下来输入 \(n\) 个零件所表示的矩阵。

对于每个零件的矩阵 \(a\),输入的格式如下:

共输入 \(h\) 行,每行 \(w\) 个元素。

\(i\) 行第 \(j\) 列的元素 \(a_{i,j}\),表示这个矩阵中第 \(i\) 行第 \(j\) 列的元素。

输出格式

输出一个整数,表示在搭建成功的情况下,大楼最多能使用多少个零件。若无法成功搭建,则直接输出 -1

样例 #1

样例输入 #1

3 2 2
0 1 
1 1
1 0
0 0
0 1
0 1

样例输出 #1

2

样例 #2

样例输入 #2

3 2 2
0 1
1 0
0 0
0 1
1 0
0 0

样例输出 #2

-1

提示

【样例1解释】

可以证明最优方案是用第一个零件和第三个零件搭大楼,用第二个零件搭小楼。

【数据范围】

本题采用捆绑测试

  • Subtask 1(30 points):\(n \le 20\)
  • Subtask 2(5 points):\(w = h = 1\)
  • Subtask 3(65 points):无特殊限制。

对于所有测试数据,\(1 \le n \le 1000\)\(1 \le w,h \le 6\)


思路

由于数据比较小,可以先预处理出任何两个零件是否能出现在同一栋大楼上。然后便可以直接转化为二分图染色问题求解。

code

#include<bits/stdc++.h>
using namespace std;
int n,h,w;
const int N=1005;
int a[1000+5][10][10];
int t[N];
vector<int>g[N];
int k[N];
int s[N];
int ans;
bool dfs(int u,int c){
	k[u]=c;s[c]++;
	for(auto v:g[u]){
		if(k[v]==k[u]) return false;
		if(k[v]==-1&&!dfs(v,1-c)) return false;
	}
	return true; 
}
int main(){
	cin>>n>>h>>w;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=h;j++)
			for(int k=1;k<=w;k++){
				cin>>a[i][j][k];
				t[i]+=a[i][j][k];
			}
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			if(i==j) continue;
			int f=0;
			for(int x=1;x<=h;x++)
				for(int y=1;y<=w;y++){
					f+=a[i][x][y]&&a[j][x][y];
				}
			if(f<(min(t[i],t[j])+1)/2){
				g[i].push_back(j);
				g[j].push_back(i);
			}
		}
	memset(k,-1,sizeof k);
	for(int i=1;i<=n;i++){
		int p0=s[0];
		int p1=s[1];
		if(k[i]==-1&&!dfs(i,1)){
			cout<<-1<<endl;
			return 0;
		}
		ans+=max(s[0]-p0,s[1]-p1);
	}
	cout<<ans<<endl;
	return 0;
}

本题后记

本题为2024年12月的[DHOI] Round 1洛谷比赛中,数据实在过水。同机房的QCdalao直接写了个假贪心过了第二和第三个包(难以想象),而FRZdalao则写出了第一个包,于是……
超级缝合怪

#ifndef ONLINE_JUDGE
#define FRZ_29
#endif

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
//using i128 = __int128;

const int N = 1005;

#define PRINT(x) cout << #x << " = " << x << "\n"
#define LF(i, l, r) for (int i = (l); i <= (r); i++)
#define RF(i, r, l) for (int i = (r); i >= (l); r++)

int n, h, w, s[N], f[N][N];
int m[N][10][10], ans;
vector<int> U;
bool vis[N];

bool check() {
  LF(i, 0, U.size() - 1) {
    LF(j, i + 1, U.size() - 1) {
      if (f[U[i]][U[j]] < (min(s[U[i]], s[U[j]]) + 1) / 2) {
        return false;
      }
    }
  }

  LF(i, 1, n) {
    if (vis[i]) continue;
    LF(j, i + 1, n) {
      if (vis[j]) continue;
      if (f[i][j] < (min(s[i], s[j]) + 1) / 2) return false;
    }
  }

  return true;
}

void dfs(int d) {
  if (d == n + 1) {
    if (check()) ans = max(ans, max((int)(n - U.size()), (int)(U.size())));
    return;
  }

  U.push_back(d);
  vis[d] = 1;
  dfs(d + 1);
  U.pop_back();
  vis[d] = 0;
  dfs(d + 1);
}
int a[1005][15][15];
int sum[1005];//f[1005][1005];
int num[1005][1005];
struct node
{
    int id,s;
}frz[1005];
bool cmp(node x,node y){return x.s<y.s;}
inline int read()
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-'){f=-1;}c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(int)(c-'0');c=getchar();}
    return x*f;
}
inline void write(int x)
{
    if(x<0){putchar('-');x=-x;}
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
inline void solve()
{   
//    cin>>n>>h>>w;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=h;j++)
            for(int k=1;k<=w;k++) cin>>a[i][j][k],sum[i]+=a[i][j][k];
    for(int oi=1;oi<=n;oi++)
    {
        for(int oj=1;oj<=n;oj++)
        {
            if(oj==oi) continue;
            for(int i=1;i<=h;i++)
                for(int j=1;j<=w;j++) f[oi][oj]=f[oi][oj]+(a[oi][i][j]&a[oj][i][j]);
        }
    }
    for(int i=1;i<=n;i++)
    {
        frz[i].id=i;
        for(int j=1;j<=n;j++)
        {
            // if(i==j) continue;
            // cout<<f[i][j]<<" "<<((min(sum[i],sum[j])+1)/2)<<endl;
            if(f[i][j]<((min(sum[i],sum[j])+1)/2))
            {
                num[i][j]=1;
            }
            frz[i].s+=num[i][j];
        }
    }
    sort(frz+1,frz+n+1,cmp);
    for(int i=1;i<=n;i++)
    {
        int kkk=frz[i].id;
        if(vis[kkk]==0)
        {
            ans++;
            for(int j=1;j<=n;j++)
            {
                if(num[kkk][j]==1)
                {
                    vis[j]=1;
                }
            }
        }
    }
    if(ans==1) cout<<-1<<endl;
    else cout<<ans<<endl;

}

int main() {
//#ifdef FRZ_29
//  freopen("code.in", "r", stdin);
//  freopen("code.out", "w", stdout);
//#endif

  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  
  cin >> n >> h >> w;
	if(n>20) {solve();return 0;} 
  LF(i, 1, n) LF(j, 1, h) LF(k, 1, w) {
    cin >> m[i][j][k];
    s[i] += m[i][j][k];
  }

  LF(i, 1, n) LF(j, i + 1, n) LF(k, 1, h) LF(p, 1, w)
    f[i][j] += (m[i][k][p] & m[j][k][p]);
  
  dfs(1);
  cout << (ans == 0 ? -1 : ans);
  return 0;
}


// START AT 2024 / 12 / 15 13 : 56 : 15

水完了。
yingxilin
Qian·JX のjoker
2024-12-18 于玉山一中

posted @ 2024-12-18 21:24  Qian·JXのjoker  阅读(24)  评论(0)    收藏  举报