题意:给你n个点,点带权,任意两点之间的边权是它们的点权的异或值中“1”的个数,问你该图的最小生成树。
看似是个完全图,实际上有很多边是废的。类似……卡诺图的思想?从读入的点出发BFS,每次只到改变它的任意一位所能到达的点(不论是否读入)。
记录每个点是从哪个读入点BFS过来的,当第二次访问某个点的时候,就将它的两个源头(一次是第一次的时候标记的,一次是第二次过来的)连一条边。
这样最多连m(位数)*n条边,实际上比这个值更小。
这种做法可以将很多显然不会出现在最小生成树里的边排除掉。
opencup的标程:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
// O(kn)
#include <bits/stdc++.h>
using namespace std;
struct FAU {
vector <int> p, r;
FAU(int n): p(n,-1), r(n,0) {}
int find(int x) {
if (p[x] == -1) return x;
return p[x] = find(p[x]);
}
void join(int x, int y) {
x = find(x); y = find(y);
if (x == y) return ;
if (r[x] > r[y]) p[y] = x;
else p[x] = y;
if (r[x] == r[y]) ++r[y];
}
};
int readBinary() {
string s;
cin >> s;
int result = 0;
for (char c : s) {
result = 2 * result + ((c == 'L') ? 1 : 0);
}
return result;
}
void solveTestcase() {
int k, n;
cin >> k >> n;
const int N = 1 << k;
vector <int> dist(N, -1), from(N);
queue <int> q;
for (int i = 0; i < n; i++) {
int val = readBinary();
dist[val] = 0;
from[val] = i;
q.push(val);
}
vector <vector <pair<int,int>>> edges(k+1);
while (!q.empty()) {
int v = q.front(); q.pop();
for (int bit = 0; bit < k; bit++) {
int u = v ^ (1 << bit);
if (dist[u] == -1) {
dist[u] = 1 + dist[v];
from[u] = from[v];
q.push(u);
} else if (from[u] != from[v]) {
int len = dist[u] + dist[v] + 1;
if (len <= k) {
edges[len].push_back({from[u], from[v]});
}
}
}
}
FAU fau(n);
int ans = 0;
for (int len = 1; len <= k; len++) for (auto &edge : edges[len]) {
if (fau.find(edge.first) != fau.find(edge.second)) {
ans += len;
fau.join(edge.first, edge.second);
}
}
cout << ans << '\n';
}
int main() {
ios_base::sync_with_stdio(false);
int z;
cin >> z;
while (z--) {
solveTestcase();
}
return 0;
}