1. 程式人生 > >【平衡樹啟發式合併】POJ1741[Tree]題解

【平衡樹啟發式合併】POJ1741[Tree]題解

題目概述

給出一棵樹,求dis(x,y)<=K的點對(x,y)有多少,dis(x,y)表示x到y的最短距離(x<y)。

解題報告

這道題是明顯的點分,就不闡述點分了,主要講講另一種解法:平衡樹+啟發式合併,效率也很不錯:O(n*log2^2(n))。

說的這麼高大上,實際上就是暴力啦。先找出根節點,然後Dfs遞迴處理。對於每一個節點,都建立一棵平衡樹(作者蒟蒻,這裡使用Treap),然後從葉往根上推(從葉往根上推根據Dfs的深度優先就可以輕鬆實現)。舉個例子,如圖:
這裡寫圖片描述
首先先把son1的Treap的所有節點(直接遍歷即可)和fa的Treap進行累加,累加後,把fa加入當son1中。處理完畢後,根據啟發式合併的思想,先比較fa的Treap的平衡樹大小和son2的Treap的平衡樹大小,把小的往大的身上累加,然後把小的加入到大的中。以此類推,直到處理完所有son。

但是如何計算兩個點之間的距離?其實很簡單(當然也可以直接用lazy_tag,留給你們自己思考啦:P):
這裡寫圖片描述
記錄dis[i]表示i節點在這棵樹中的深度。由圖所示,son1和son2的距離就是dis[son1]+dis[son2]-2*dis[fa],不難發現2*dis[fa]可以獨立領出來統一減去,那麼問題就簡化為dis[son1]+dis[son2]了。
ps:這樣的話,就有一個細節了:fa不能在剛開始就加入Treap,否則統計會出錯(fa和其他節點的距離是dis[son]-dis[fa],減去2*dis[fa]會出錯)。

其實至此,這道題目就講完了,再來談談啟發式合併複雜度的問題:由於是小的加入到大的身上,所以原先小的樹的節點所在的樹的個數必定*2,而樹的個數最多是n。所以每個節點頂多被加入log2(n)次,而每次加入的複雜度是log2(n),所以總時間複雜度為O(n*log2^2(n))。

示例程式

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int maxn=10005,maxm=20005,maxt=140005;

struct Treap
{
    Treap *son[2];
    int x,w,si,p;
    int cmp(int k) {if (k<x) return 0; else if (k>x) return 1;return -1;}
    void
Pushup() {si=w+son[0]->si+son[1]->si;} }; //Treap的結構體 Treap tem[maxt],*null=&tem[0],*len=null,*ro[maxn]; //用null代替NULL int n,K,son[maxm],nxt[maxm],lnk[maxn],w[maxm],E,ans; bool vis[maxn]; void Add(int x,int y,int z) {son[++E]=y;w[E]=z;nxt[E]=lnk[x];lnk[x]=E;} Treap *newTreap(int k,int num) {len++;len->w=len->si=num;len->x=k;len->p=rand();len->son[0]=len->son[1]=null;return len;} void rotate(Treap* &id,int f) { Treap *t=id->son[f^1];id->son[f^1]=t->son[f];t->son[f]=id; id->Pushup();t->Pushup();id=t; } void Insert(Treap* &id,int k,int num) { if (id==null) {id=newTreap(k,num);return;} int f=id->cmp(k);id->si+=num; if (f==-1) id->w+=num; else { Insert(id->son[f],k,num); if (id->son[f]->p>id->p) rotate(id,f^1); } } int Asksum(Treap* id,int k) //詢問<=k的節點有多少 { if (id==null) return 0; int f=id->cmp(k); if (f==-1) return id->son[0]->si+id->w; else if (f==0) return Asksum(id->son[0],k); else if (f==1) return id->son[0]->si+id->w+Asksum(id->son[1],k); } void Join(Treap* &A,Treap* B) //把B插入A中 { if (B==null) return; Insert(A,B->x,B->w); Join(A,B->son[0]);Join(A,B->son[1]); } int Count(Treap* A,Treap* B,int dis) //統計B所有節點在A中的滿足個數 { if (B==null) return 0; return B->w*Asksum(A,dis-B->x)+Count(A,B->son[0],dis)+Count(A,B->son[1],dis); //注:這裡一定要乘上B->w,因為B->x不一定只有一個 } void Dfs(int x,int dep) { vis[x]=true; for (int j=lnk[x];j;j=nxt[j]) if (!vis[son[j]]) { Dfs(son[j],dep+w[j]); //先處理下面 if (ro[x]->si<ro[son[j]]->si) swap(ro[x],ro[son[j]]); //啟發式合併,小的往大的身上累加和合並 ans+=Count(ro[x],ro[son[j]],K+2*dep);Join(ro[x],ro[son[j]]); } ans+=Asksum(ro[x],K+dep); //最後統計x Insert(ro[x],dep,1); //把x插入進去 } int main() { freopen("program.in","r",stdin); freopen("program.out","w",stdout); for (scanf("%d%d",&n,&K);n||K;scanf("%d%d",&n,&K)) { memset(lnk,0,sizeof(lnk));E=0;memset(vis,0,sizeof(vis));ans=0; for (int i=1;i<=n;i++) ro[i]=null;len=null; for (int i=1;i<=n-1;i++) { int x,y,z;scanf("%d%d%d",&x,&y,&z); Add(x,y,z);Add(y,x,z); } Dfs(1,0); printf("%d\n",ans); } return 0; }

相關推薦

平衡啟發式合併POJ1741[Tree]題解

題目概述 給出一棵樹,求dis(x,y)<=K的點對(x,y)有多少,dis(x,y)表示x到y的最短距離(x<y)。 解題報告 這道題是明顯的點分,就不闡述點分了,主要講講另一種解法:平衡樹+啟發式合併,效率也很不錯:O(n*log2^2(n)

樓天城男人八題分治|Treap+啟發式合併POJ1741 Tree

題面在這裡 待我先膜拜一下樓教主…… 首先這題是很明顯的樹分治 想說點什麼卻發現已經沒什麼好說了 然後我們來看另一種解法:平衡樹亂搞 這裡用的是Treap實現 對於每個節點,用Treap記錄該子樹每個節點到根(預設為1)的距離 那麼如何統計答案

BZOJ2212Tree Rotations(POI2011)-平衡啟發式合併

測試地址:Tree Rotations 做法:本題需要用到平衡樹啟發式合併。 對於葉子節點,最優答案顯然是00。然後對於每棵子樹,我們發現由轉換它的左右子樹所多出的逆序對數,僅和兩邊都有什麼數字有關,而不和兩邊的數字順序有關,所以我們對於每個葉子節點儲存一棵

BZOJ1483[HNOI2009]夢幻布丁(平衡啟發式合併+並查集)

題目: BZOJ1483 分析: (這題碼了一下午,碼了近250行,但是意外跑的比本校各位神仙稍快,特寫部落格紀念) 首先能看出一個顯然的結論:顏色段數只會變少不會變多。 我們考慮用並查集維護區間,對於每個區間維護它的起點和終點。建\(n\)棵平衡樹,第\(i\)棵存顏色為\(i\)的區間。把\(x

ZOJ 4053青島網路賽主席+啟發式合併

題意:       給你一個數組,每次給你一個數,將這個數從整個陣列中刪去。然後陣列被劃分成了多個小區間,問你各個區間中最大的逆序對是多少。   思路:       首先建立一顆主席樹維護區間[1,x]的資訊。

1058. [ZJOI2007]報表統計平衡-splay+堆

++ insert pop sizeof tar output span body for Description   小Q的媽媽是一個出納,經常需要做一些統計報表的工作。今天是媽媽的生日,小Q希望可以幫媽媽分擔一些工 作,作為她的生日禮物之一。經過仔細觀察,小Q發

啟發式合併線段平衡

【啟發式合併】線段樹,平衡樹 啟發式合併就是一種複雜度可以證明的貪心合併 平衡樹啟發式合併: 對於平衡樹的啟發式合併,我們將一個 $size$ 較小平衡樹一個一個結點暴力加入 $size$ 較大的平衡樹中 最壞時間複雜度是玄學的 $O(N log^{2} N)$ 空間複雜度 $O(N)$ 模板題:

BZOJP2212&P3702Poi2011Tree Rotations二叉題解啟發式合併

啟發式合併不解釋 Code: #include<bits/stdc++.h> using namespace std; const int maxn=4e5+5; struct node{ int val,key,size,s; nod

CF741D-Arpa's letter-marked tree and Mehrdad's Dokhtar-kosh paths樹上啟發式合併

正題 評測記錄:https://www.luogu.org/recordnew/lists?uid=52918&pid=CF741D 題目大意 一棵根為 1

Trie啟發式合併2019雅禮集訓 matrix

題目: 定義一個矩陣的貢獻為:其互不相同的行的種類數。 給出一個矩陣,求其所有子矩陣的貢獻和。 分析: 可以把每一行拿出來,弄成一個字串,建一顆Trie樹出來。 此時,就可以算出以最左端為左邊界的所有子矩陣的貢獻。 算完後,把第一層節點合併,相當於去除了第一列的

CF 600 ELomsat gelral——樹上啟發式合併dsu on tree入門

一.前言: 這次講解的dsu on tree,其實是一種優美的暴力,它的時間複雜度分析與樹鏈剖分類似,也需要用到重兒子這一概念(不會樹剖的點這裡). 這種演算法適用於一類樹上查詢子樹的問題,不過它只能處理有子樹查詢的問題,不支援修改,也不支援鏈查詢. 二.例題:

3223. 文藝平衡平衡-splay

bsp ever tar ++i %d 變換 mar oid n+1 Description 您需要寫一種數據結構(可參考題目標題),來維護一個有序數列,其中需要提供以下操作:翻轉一個區間,例如原有序序列是5 4 3 2 1,翻轉區間是[2,4]的話,結果是5 2

1208. [HNOI2004]寵物收養場平衡-splay

過程 處理 過多 前驅 順序 urn 想要 esc 相同 Description 最近,阿Q開了一間寵物收養所。收養所提供兩種服務:收養被主人遺棄的寵物和讓新的主人領養這些寵物。每個領養者都希望領養到自己滿意的寵物,阿Q根據領養者的要求通過他自己發明的一個特殊的公式,得出

1251. 序列終結者平衡-splay

gpo name blog swap push cpp turn AC ace Description 網上有許多題,就是給定一個序列,要你支持幾種操作:A、B、C、D。一看另一道題,又是一個序列 要支持幾種操作:D、C、B、A。尤其是我們這裏的某人,出模擬試題,居然

1861. [ZJOI2006]書架平衡-splay

LG 困難 錯誤 哨兵 sin div 整數 能夠 let Description 小T有一個很大的書櫃。這個書櫃的構造有些獨特,即書櫃裏的書是從上至下堆放成一列。她用1到n的正整數給每本書都編了號。 小T在看書的時候,每次取出一本書,看完後放回書櫃然後再拿下一本。由

3196. 二逼平衡線段套splay

== ins stream ostream %d 需要 output edi afa Description 您需要寫一種數據結構(可參考題目標題),來維護一個有序數列,其中需要提供以下操作: 1.查詢k在區間內的排名 2.查詢區間內排名為k的值 3.修改某一位

BZOJ2212 [Poi2011]Tree Rotations 線段合並

namespace 二叉 情況 一點 sizeof mem IV efi ota 題目鏈接 BZOJ2212 題解 一棵子樹內的順序不影響其與其它子樹合並時的答案,這一點與歸並排序的思想非常相似 所以我們只需單獨處理每個節點的兩棵子樹所產生的最少逆序對即可 只有兩種情況,要

平衡2018國慶三校聯考D3T3

分析: #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<assert.h> #def

codeforces 1076E Vasya and a Tree dfs+狀陣列

題目:戳這裡 題意:給定有n個點的一棵樹,頂點1為根。m次操作,每次都把以v為根,深度dep以內的子樹中所有的頂點(包括v本身)加x。求出最後每個點的值為多少。 解題思路:考慮到每次都只對點及其子樹操作,要用dfs。設v當前要操作的點,操作的深度是dep,d[v]表示v的深度。要把深度[d[v],d[v]

平衡+掃描線優化建圖LGT51927反射

【題目】 原題地址 題意: 一個二維平面,給定一個初始平臺和nnn個能量平臺,均平行xxx軸。 有兩種能量發射器: typ1typ1typ1:若安裝在平臺上方,向右上45度發射,在下方則向右下45度發射 typ2typ2typ2:同時向右上和右下45度發射 當能