日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

牛客多校5 - Graph(字典树+分治求最小生成树)

發布時間:2024/4/11 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 牛客多校5 - Graph(字典树+分治求最小生成树) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

題目鏈接:點擊查看

題目大意:給出一棵樹,每條邊都有一個權值,每次操作可以刪除任意一條邊或者增加任意權值的一條邊,現在可以執行數次操作,不過任何時間都要滿足以下兩個條件:

  • n 個點互相連通
  • 所有環的權值異或和為 0
  • 求數次操作后圖上邊權之和的最小值

    題目分析:將題意轉換一下就可以轉換為經典問題:完全圖上的最小生成樹,給出 n 個點,每個點都有權值 a[ i ] ,每條邊的權值為 a[ i ] ^ a[ j ] 現在需要求最小生成樹

    這個題目只需要 dfs 跑一遍就可以轉換為上述問題了,這里不多贅述,那么將問題轉換后,該如何求解呢

    有個 boruvka 算法也是求解最小生成樹問題的,只不過是克魯斯卡爾算法和普雷姆算法的結合版本,具體就是維護圖中的所有連通塊,然后貪心去找每一個連通塊和其余所有的連通塊之間的最小邊權,將其合并為一個連通塊,如此往復

    那么和這個題目有什么聯系呢?首先拋出一個問題:把當前圖的點集隨意劃分成兩半,遞歸兩半后選出連接兩個點集的邊中權值最小的一條,得到最后的最小生成樹。

    乍一看沒什么問題,但仔細想一下就會發現這個策略其實是錯誤的,因為最終的最小生成樹中可能有兩條連接當前層兩個點集的邊

    但是對于本題而言,我們可以借助01字典樹劃分點集,借一下圖:https://www.cnblogs.com/qieqiemin/p/13381095.html

    當我們在字典樹上從最高位到最低位維護每一個數字后,顯然每一個節點的左右兩個兒子,就已經將所有的點劃分為兩個集合了

    以上圖中的劃分方法為例,假設兩個集合是從第 deep 層的高度分開,顯然兩個集合中 deep 往上的位置在二進制下都是相同的,不同的只是 deep - 1 及往下的位置,所以合并上圖中兩個綠色集合中的點集所需要的最小代價就是:(1<<deep)+找到兩個點 i 和 j ,滿足點 i 屬于左邊的點集,j 屬于右邊的點集,同時 a[ i ] ^ a[ j ] 是最小的

    這樣貪心由下往上去合并的話,上面那個錯誤的策略,在這個題目中的正確性得到了保證

    綜上所述,總結一下本題的求解方法,在計算出每個點的權值后,將其放入01字典樹中,然后對于每一層深度分治即可,需要注意的幾個地方就是,這個題目每個點的范圍是 0 ~ n-1 ,而不是 1 ~ n,還有就是對于點權相同的點來說,我們可以想象在其之間建立一條邊,因為花費為 0 ,所以不會對答案造成影響,故在處理時,對點權去重一下可以減少時間復雜度

    至于實現,對于每次左右子樹中的兩個連通塊,我是vector暴力維護的點集(向上傳遞完畢后不要忘記及時初始化,防止爆內存),然后遍歷點集數更小的連通塊,在另一個連通塊中找異或值最小的答案,這個就是01字典樹的基本應用了,所以字典樹在本題中共有兩個用途,一個是基本應用,也就是貪心去查找異或值最小的答案,另一個是結合最小生成樹的貪心策略,將其分塊處理

    時間復雜度的話,因為字典樹的高度為30,所以分治+vector暴力維護點集的時間復雜度為 nlogn,加上需要在字典樹上查找異或的最小值,需要多加一個log,總的時間復雜度也就是 nlognlogn 了

    代碼打注釋了,有不理解的地方看看代碼應該就明白了

    代碼:
    ?

    #include<iostream> #include<cstdio> #include<string> #include<ctime> #include<cmath> #include<cstring> #include<algorithm> #include<stack> #include<climits> #include<queue> #include<map> #include<set> #include<sstream> #include<cassert> #include<bitset> using namespace std;typedef long long LL;typedef unsigned long long ull;const int inf=0x3f3f3f3f;const int N=1e5+100;struct Node {int to,w;Node(int to,int w):to(to),w(w){} };vector<Node>node[N];vector<int>p[N*32],a;int trie[N*32][2],cnt=1;int newnode()//動態初始化 {cnt++;trie[cnt][0]=trie[cnt][1]=0;return cnt; }void insert(int x)//字典樹的插入 {int pos=1;for(int i=30;i>=0;i--){int to=(x>>i)&1;if(!trie[pos][to])trie[pos][to]=newnode();pos=trie[pos][to];}p[pos].push_back(x); }int search(int x,int pos,int deep)//字典樹的查詢,x:需要查詢的值,pos:當前根節點,deep:深度 {int ans=0;for(int i=deep;i>=0;i--){int to=(x>>i)&1;if(trie[pos][to])pos=trie[pos][to];else{pos=trie[pos][!to];ans|=(1<<i);}}return ans; }void dfs(int u,int fa,int val) {a.push_back(val);for(auto it:node[u]){int v=it.to,w=it.w;if(v==fa)continue;dfs(v,u,val^w);} }int query(int ls,int rs,int deep)//在ls的子樹和rs的子樹中找到權值最小的邊 {int ans=INT_MAX;if(ls>rs)swap(ls,rs);for(int i=0;i<p[ls].size();i++)//暴力枚舉大小較小的一個點集,logn去另一個點集中查詢ans=min(ans,search(p[ls][i],rs,deep-1));return ans; }LL solve(int rt,int deep)//分治,rt:根節點,deep:深度 {LL ans=0;int ls=trie[rt][0],rs=trie[rt][1];if(ls)//記錄左子樹中連通塊的答案ans+=solve(ls,deep-1);if(rs)//記錄右子樹中連通塊的答案ans+=solve(rs,deep-1);if(ls&&rs)//如果需要合并,找到異或和最小的邊進行連接ans+=query(ls,rs,deep)+(1<<deep);p[rt].insert(p[rt].end(),p[ls].begin(),p[ls].end());//暴力維護點集p[rt].insert(p[rt].end(),p[rs].begin(),p[rs].end());p[ls].resize(0),p[rs].resize(0);//別忘了重置return ans; }int main() { #ifndef ONLINE_JUDGE // freopen("data.in.txt","r",stdin); // freopen("data.out.txt","w",stdout); #endif // ios::sync_with_stdio(false); int n;scanf("%d",&n);for(int i=1;i<n;i++){int u,v,w;scanf("%d%d%d",&u,&v,&w);u++,v++;node[u].push_back(Node(v,w));node[v].push_back(Node(u,w));}dfs(1,-1,0);//將邊權轉換為點權sort(a.begin(),a.end());a.erase(unique(a.begin(),a.end()),a.end());//離散化去重for(auto v:a)//將不同的點插入到字典樹中insert(v);printf("%lld\n",solve(1,30));return 0; }

    ?

    總結

    以上是生活随笔為你收集整理的牛客多校5 - Graph(字典树+分治求最小生成树)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。