UOJ176新年的繁榮
阿新 • • 發佈:2019-01-21
中聯通 code ret getch ostream 怎麽辦 iostream read def
復雜度\(O((n+2^m)mlogn)\)
那麽肯定之前就連過邊了。不會再連一次。
復雜度\(O(2^mm\alpha(n))\)
題目鏈接
Boruvka生成樹算法
\(Boruvka\)算法就是先把每個點看作一個聯通塊,然後不斷在聯通塊之間找最優的邊進行合並。因為每次聯通塊的數量最少縮小一半。所以合並次數是\(log\)的
先把所有的點權掛到\(trie\)樹上。然後對於每個聯通塊進行合並的時候。對於聯通塊中的每個點都去\(trie\)上搜索他能找到的最優秀的邊。也就是說如果當前位置是\(1\)那麽就搜索1子樹,否則的話既要搜0子樹,也要搜1子樹。這樣1子樹是一定要搜的。所以把0子樹變為1子樹和0子樹合並起來的結果。然後就可以搜索了。
還有一個問題就是。如果當前子樹中的所有點都已經在這個聯通塊裏了怎麽辦。所以統計出每棵子樹中聯通塊編號的最大值和最小值。然後就可以知道當前子樹中是不是還有不屬於這個聯通塊裏的點了。合並聯通塊之後再把每個點合並的聯通塊裏的點所屬的聯通塊修改一下就行了。
更優秀的做法
上面的做法代碼長且思路復雜。有一種更好的做法。
先把所有權值相同的點連一條邊。這樣肯定會比較優秀。
然後考慮枚舉最終答案中\(w[u]\&w[v]\)的值p。
因為\(x\&w[u] \leq w[u]\),倒著枚舉p,然後找到一個點u使得\(w[u]\&p=p\)。然後從其他的等於滿足\(w[v]\&p=p\)的點\(v\)中找一個與\(u\)不在同一個聯通塊裏的點。將這兩個點之間連邊。貢獻為\(p\)。
萬一\(w[u]\&w[v]\)比\(p\)大呢。可以證明這是不可能的。因為p是倒著枚舉的,如果\(w[u]\&w[v]>p\)
復雜度\(O(2^mm\alpha(n))\)
代碼
/* * @Author: wxyww * @Date: 2019-01-21 15:46:58 * @Last Modified time: 2019-01-21 15:53:01 */ #include<cstdio> #include<iostream> #include<cstdlib> #include<cmath> #include<ctime> #include<bitset> using namespace std; typedef long long ll; const int N = 1000000 +10; ll read() { ll x=0,f=1;char c=getchar(); while(c<'0'||c>'9') { if(c=='-') f=-1; c=getchar(); } while(c>='0'&&c<='9') { x=x*10+c-'0'; c=getchar(); } return x*f; } int p[N],fa[N]; ll ans; int find(int x) { return fa[x] == x ? x :fa[x] = find(fa[x]); } void uni(int x,int y) { x = find(x),y = find(y); if(rand() & 1) fa[x] = y; else fa[y] = x; } int main() { srand(time(0)); int n = read(),m = read(); for(int i = 1;i <= n;++i) { int x = read(); if(p[x]) ans += x; p[x] = x; } int k = (1 << m); for(int i = 1;i <= k;++i) fa[i] = i; for(int i = (1 << m) - 1;i;--i) { for(int j = 0;j < m && !p[i];++j) p[i] = p[i | (1 << j)]; int u = p[i]; if(!u) continue; for(int j = 0;j < m;++j) { int v = p[i | (1 << j)]; if(!v) continue; if(find(v) != find(u)) { ans += i; uni(u,v); } } } cout<<ans; return 0; }
UOJ176新年的繁榮