最近打的區域賽模擬裡用到了最小異或生成樹
於是進行了一手學習(
問題背景:
有乙個序列,每個點有乙個權值$a_i$。
取一條邊$i$ -> $j$的代價是$a_i xor a_j$
問這個圖的最小生成樹的邊權和
前置知識
trie樹
演算法原理
1、borůvka演算法(用於求解最小生成樹)
做法:
開始時,每個點都是乙個獨立的連通塊。每次對於乙個連通塊,去找最短邊,其中邊的乙個端點在連通塊內,而另乙個不在,將這些邊加入答案的最小生成樹中,並且用並查集更新聯通狀態,直到沒有邊可以新增。
效率分析:
每次聯通塊的數量至少會減半,而每次詢問要訪問整個邊集
所以總複雜度是$o(|e| log n)$
2、最小異或生成樹
題目中求解最小生成樹,如果按照kruskal來的話需要把所有邊都列出來,是$n^2$的效率,顯然會t飛
考慮把序列中的數字按照從高位到低位分組,按照該位置是0還是1來分
這個和字典樹的操作正好對應
於是我們就發現,如果我們每次要把兩個點接起來的話,顯然在同乙個lca的子樹內是最優的,因為它高位的異或全部是$0$,如果離開這個子樹而去取其他子樹的點的話,則一定會在更高位置產生貢獻,答案不會更優
發現現在問題就變成了 讓左子樹聯通,右子樹聯通 然後讓左右子樹聯通
左子樹和右子樹顯然是原問題的子問題,現在考慮第三個問題要怎麼解決
類似於dfs序的考慮,我們在對這個字典樹進行先根遍歷的時候,得到的序列正好是從小到大的
於是我們可以先對序列進行排序,此時每個點的子樹裡所包含的數字反映在原序列上就是一段連續的區間
每次合併左右子樹的時候,暴力的把比較小的子樹中的每乙個權值在另乙個trie樹上跑即可(求最大/最小異或,trie的經典操作)
3、題目
problem - 888g - codeforces
模板,就是問題背景描述的問題
#includeusingnamespace
std;
intn,cnt;
int a[200005],l[31*200005],r[31*200005],tree[31*200005][3
];void insert(long
long x,int
pos)
else
}}long
long query(int now,int pos,long
long
x)
return
ans;
}long
long getans(int rt,int
pos)
}else
return getans(x,pos-1)+getans(y,pos-1)+ans;
}if (pos==0) return0;
}int
main()
最小異或生成樹Xor MST
1.將每個點的權值轉換成二進位制,從高位往低位依次插入01字典樹.2.dfs遍歷該字典樹的每個結點,如果某個結點有兩個子節點,這兩個子節點的子樹分別會構成兩個連通塊,要在這兩個連通塊之間各選乙個點連邊並使它們的異或值最小,通過find函式遞迴處理這個問題.ans還要加上此時產生的二進位制位對應的值....
CF888G XOR MST 最小異或生成樹
cf888g trie上貪心,先左右兩邊連邊,再用一條邊的代價連起左右兩顆樹。因為內部的邊一定比跨兩棵樹的邊權笑,顯然是對的。自己瞎yy的。啟發式合併 include define ll long long using namespace std const int 2e5 7 int n,a ch...
異或最小生成樹
對於n個點的完全圖,每兩個點之間的距離等於兩點的權值的異或和,求最小生成樹 int n struct xortrie void insert ll x,int id val rt x ll answerpos int rt,int pos,ll x return rt void traceback ...