最大团问题

题意

给出 n n n 个数字 n n n 个字母,在 m m m 组数据中匹配数字和字母之间的关系,使尽量多的数据都能满足同一个字母与数字匹配的关系,求最多的数据个数。

思路

看到题目,容易想到存下每一个数据中字母和数字之间的关系,在进行非常暴力的匹配求解。用样例解释,关系就是

数据 1 : A1 B0 C3
数据 2 : A2 B0 C1
数据 3 : A0 B3 C1

将其简化为

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

我们就把题目转变为每组数字之间匹配的题目。

其中,由于 0 代表这组数据中没有出现这一字母,所以匹配的时候 0 可以和任何数字进行匹配(兼容)。

接着,每两组数据之间进行匹配 ( O ( n 2 ) O(n^2) O(n2) ),如果匹配成功我们就将它们连一条边(编号之间连边)。

此时的图就变为

1 2-3

多组数据都要匹配才能算入答案,所以在我们最后选入答案的数据所形成的图应该是一个完全图(任意两个数据都可以匹配)。

这时候,我们就可以把问题再次简化为在原来的图中找到最大的完全子图(最大团问题)。

事情就变得简单了,最后,我们可以用一种回溯思想的算法 (Bron-Kerbosch算法)解决( O ( n 3 ) O(n^3) O(n3))。这个问题就解决了。

补充

Bron-Kerbosch算法:

每次往原有的最大团中尝试加点 x x x(从 1 ∼ n 1\sim n 1n 从小到大取),每次判断 x x x 是否有和前面 x − 1 x - 1 x1 个结点相连。对其进行剪枝,如果当前取的结点个数加上剩余能取得结点个数大与当前最优解,那么当前结点就不用选,从下一个开始选。

算法参考此处

代码

#include <bits/stdc++.h>

using namespace std;
const int maxn = 1e2 + 10;

int graph[maxn][maxn];
bool nwx[maxn], bestx[maxn];
int bestn;
int n, m, s;

bool place(int x) {
	for (int i = 1; i < x; ++i)
		if (nwx[i] && !graph[x][i]) return false;
	return true;
}

void backtrack(int x) {
	if (x > n) {
		for (int i = 1; i <= n; ++i) bestx[i] = nwx[i];
		bestn = s;
		return;
	}
	if (place(x)) {
		nwx[x] = 1, s++;
		backtrack(x + 1);
		s--;
	}
	if (s + n - x > bestn) {
		nwx[x] = 0;
		backtrack(x + 1);
	}
}

void Max_Clique() {
	backtrack(1);
	cout << bestn << '\n';
//	for (int i = 1; i <= n; ++i) if (bestx[i]) cout << i << ' ';
}

int N, M;
int f[maxn][maxn];

bool ifMate(int x, int y) {
	for (int i = 1; i <= N; ++i)
		if (f[x][i] != f[y][i] && f[x][i] != 0 && f[y][i] != 0) return false;
	return true;
}

bool vis[maxn];

signed main() {
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> N >> M;
	string s;
	for (int i = 1, len; i <= M; ++i) {
		cin >> s;
		len = s.size();
		for (int j = 1, x, a; j <= len; ++j) {
			cin >> a;
			x = (s[j - 1] - 'A' + 1);
			f[i][x] = a;
		}
	}
	for (int i = 1; i <= M; ++i)
		for (int j = i + 1; j <= M; ++j) {
			if (!ifMate(i, j)) continue;
			graph[i][j] = graph[j][i] = 1;
		}
	n = M;
	Max_Clique();
}
  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值