题意
给出 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 1∼n 从小到大取),每次判断 x x x 是否有和前面 x − 1 x - 1 x−1 个结点相连。对其进行剪枝,如果当前取的结点个数加上剩余能取得结点个数大与当前最优解,那么当前结点就不用选,从下一个开始选。
代码
#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();
}