题意:给你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 << ' '; } int main() { ios_base::sync_with_stdio(false); int z; cin >> z; while (z--) { solveTestcase(); } return 0; }