題意:給你n個點,點帶權,任意兩點之間的邊權是它們的點權的異或值中「1」的個數,問你該圖的最小生成樹。ios
看似是個徹底圖,實際上有不少邊是廢的。相似……卡諾圖的思想?從讀入的點出發BFS,每次只到改變它的任意一位所能到達的點(不管是否讀入)。c++
記錄每一個點是從哪一個讀入點BFS過來的,當第二次訪問某個點的時候,就將它的兩個源頭(一次是第一次的時候標記的,一次是第二次過來的)連一條邊。spa
這樣最多連m(位數)*n條邊,實際上比這個值更小。blog
這種作法能夠將不少顯然不會出如今最小生成樹裏的邊排除掉。ci
opencup的標程:string
#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; }