[BZOJ3702][BZOJ2212]-線段樹合併
說在前面
第二次寫線段樹合併=w=
但是對複雜度還不是很明白。看到zyf2000的blog裡有提到:深度是
寫的時候狀態不怎麼好,把Insert函式裡一個小於等於寫成了大於等於,用printf查了好久才發現…
題目
題面
現在有一棵二叉樹,所有非葉子節點都有兩個孩子。在每個葉子節點上有一個權值(有n個葉子節點,滿足這些權值為1..n的一個排列)。可以任意交換每個非葉子節點的左右孩子。
要求進行一系列交換,使得最終所有葉子節點的權值按照中序遍歷寫出來,逆序對個數最少。
輸入輸出格式
輸入格式:
第一行一個整數N,表示葉子節點個數。
下面每行包含一個整數x
如果x為0,表示這個節點非葉子節點,遞迴地向下讀入其左孩子和右孩子的資訊。
如果x不為0,表示這個節點是葉子節點,且該節點權值為x。
輸出格式:
輸出交換後最少的逆序對個數
解法
算是一道比較基礎的線段樹合併題
注意到題目說是可以隨意交換子樹的。那麼對於一個非葉子節點u來說(假設兩個兒子是x和y,並且x和y均已處理完畢),x和y要麼保持原樣要麼交換,把這兩種方式的代價取min累加到答案裡就可以了,而不需要真正的去交換子樹,因為子樹內的順序對子樹之間的貢獻是沒有影響的(me略傻,這裡想了很久才想明白=A=)。
那麼計算代價的話用值域線段樹
如果把x放在y的左邊,那麼對答案有貢獻的部分就是x->Rsize
如果把y放在x的左邊,那麼對答案有貢獻的部分就是y->Rsize
在合併線段樹遞迴的時候順便計算
下面是自帶大常數的程式碼
除錯資訊懶得刪除了…
需要看程式碼的話,還是複製到本地的編譯器上看吧=w=
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int N ;
struct Tree{
int val ;
Tree *ls , *rs ;
}tc[400005] , *ttc = tc , *treeRoot;
struct Node{
long long siz ;
Node *ls , *rs ;
void update(){
siz = 0 ;
if( ls ) siz += ls->siz ;
if( rs ) siz += rs->siz ;
}
}*root[400005] , w[400005*20] , *tw = w ;
void readTree( Tree *&nd ){
if( !nd ) nd = ++ttc ;
scanf( "%d" , &nd->val ) ;
if( nd->val ) return ;
readTree( nd->ls ) ;
readTree( nd->rs ) ;
}
void Insert( Node *&nd , int lf , int rg , int val ){
if( !nd ) nd = ++tw ;
if( lf == rg ){
nd->siz = 1 ;
return ;
}
int mid = ( lf + rg ) >> 1 ;
if( val <= mid ) Insert( nd->ls , lf , mid , val ) ;
else Insert( nd->rs , mid+1, rg , val ) ;
nd->update() ;
}
long long Lcost , Rcost , ans ;
/*void print( Node *nd ){
printf( "(%lld %lld)\n", nd->ls?nd->ls->siz:0 , nd->rs?nd->rs->siz:0 ) ;
}*/
void Merge( Node *&nd , Node *x , Node *y , int lf , int rg ){
// printf( "%d %d %d [%d %d]\n" , nd , x , y , lf , rg ) ;
if( !x ){
nd = y ;
// printf( "[%d %d] No x at %lld nd(%d)\n" , lf , rg , y?y->siz:0 , nd ) ;
return ;
}
if( !y ){
nd = x ;
// printf( "[%d %d] No y at %lld nd(%d)\n" , lf , rg , x->siz , nd ) ;
return ;
}
if( !nd ) nd = ++tw ;
int mid = ( lf + rg ) >> 1 ;
if( x->rs && y->ls ) Lcost += 1LL * x->rs->siz * y->ls->siz ;
if( y->rs && x->ls ) Rcost += 1LL * y->rs->siz * x->ls->siz ;
// print( nd ) ;
Merge( nd->ls , x->ls , y->ls , lf , mid ) ;
// print( nd ) ;
// printf( "pot (%d %d)\n" , nd->ls , nd->rs ) ;
Merge( nd->rs , x->rs , y->rs , mid+1 , rg ) ;
// print( nd ) ;
// printf( "pot (%d %d)\n" , nd->ls , nd->rs ) ;
nd->update() ;
}
/*void print( Node *nd , int lf , int rg ){
int mid = ( lf + rg ) >> 1 ;
if( nd->ls ) print( nd->ls , lf , mid ) ;
printf( "%d [%d %d] siz(%lld)\n" , nd , lf , rg , nd->siz ) ;
if( nd->rs ) print( nd->rs , mid+1, rg ) ;
}*/
void dfs( Tree *nd ){
if( !nd || nd->val ) return ;
dfs( nd->ls ) ; dfs( nd->rs ) ;
Lcost = Rcost = 0 ;
// printf( "Mer : %d %d %d\n" , nd-tc , nd->ls-tc , nd->rs-tc ) ;
/* if( nd->ls-tc == 2 ){
print( root[ nd->ls-tc ] , 1 , N ) ;
print( root[ nd->rs-tc ] , 1 , N ) ;
}
*/
Merge( root[nd-tc] , root[ nd->ls-tc ] , root[ nd->rs-tc ] , 1 , N ) ;
// printf( "Mer ended , siz = %lld\n" , root[nd-tc]->siz ) ;
ans += min( Lcost , Rcost ) ;
}
void solve(){
for( Tree *tmp = tc + 1 ; tmp <= ttc ; tmp ++ )
if( tmp->val ) Insert( root[tmp-tc] , 1 , N , tmp->val ) ;
dfs( treeRoot ) ;
printf( "%lld" , ans ) ;
}
int main(){
scanf( "%d" , &N ) ;
readTree( treeRoot ) ;
solve() ;
}
相關推薦
[BZOJ3702][BZOJ2212]-線段樹合併
說在前面 第二次寫線段樹合併=w= 但是對複雜度還不是很明白。看到zyf2000的blog裡有提到:深度是log(N)的N條鏈,時間複雜度上限是Nlog(N)的,然後空間複雜度不超過時間複雜度…暫時感性理解一下,以後有時間了去看看證明 寫的時候狀態不怎麼好
bzoj2212(線段樹合併第一道)
話說像這樣的,維護的東西需要資料結構且需要合併的問題,就可以考慮合併。例如本題,我們所需要從葉子節點把維護的資料不斷遞推上來,所以就需要線段樹合併。 肯定是動態開節點 這樣的題如果寫平衡樹啟發式合併的話,就要帶兩個log,而線段樹合併一個log就可以,雖然每一次合併複雜
[BZOJ2212][Poi2011]Tree Rotations(線段樹合併)
Address 洛谷 P3521 BZOJ 2212 LOJ #2163 Solution 非常有意思的題 一個直觀的想法 對於一個點 u
Luogu3521/BZOJ2212 POI2011 Tree rotations 線段樹合併
傳送門——Luogu 傳送門——BZOJ 葉子節點的權值兩兩不同,考慮線段樹合併進行統計。 首先有一個顯然的貪心策略:如果在某一個節點處,交換左右子樹會使得這一棵子樹內的逆序對個數更少,就一定要交換,因為現在交不交換對以後的狀態不會產生影響。 然後我們考慮如何計算交換或者不交換的逆序對數量。對於一
【BZOJ2212】【POI2011】Tree Rotations(線段樹合併)
Description Solution 對於每個節點有一棵權值線段樹,向上遞迴時合併同時計算逆序對即可。 Source /*****************************
[BZOJ2212]二叉樹:線段樹合併
這道題給人的第一感覺像是樹形,我們可以按照樹形的思路進行遞迴處理,統計一個結點左右子樹貢獻的答案時,我們分別算出是否交換左右子樹的答案,取較大值並向上更新(這樣是符合最優子結構的)。問題來了,我們如何快速把兩棵子樹合併並把答案貢獻到父節點呢?線段樹可以做到。為了讓線段樹支援合
【題解】[牛客網NOIP賽前集訓營-提高組(第一場)]C.保護 LCA+線段樹動態開點+線段樹合併
題目連結 ___ #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=2e5+10; int n,m,hd[N],to
POI2011]ROT-Tree Rotations,洛谷P35231,線段樹合併
正題 給一顆n各節點的二叉樹,每個節點可以交換左右子樹,求先序遍歷的最小值。 這題很明顯,交換兩棵子樹,子樹內的逆序對不變的,變的只是左右兩邊出現的逆序對,那麼每一個葉子節點開一棵權值線段樹,每次向上合併
jzoj5926 naive 的圖 最小生成樹+啟發式合併+線段樹合併
Description 眾所周知,小 naive 有一張 n 個點,m 條邊的帶權無向圖。第 i 個點的顏色為 ci。d(s, t)表示從點 s 到點 t 的權值最小的路徑的權值,一條路徑的權值定義為路徑上權值最大的邊的權值。 求所有滿足 u < v, |cu − cv|
BZOJ.5417.[NOI2018]你的名字(字尾自動機 線段樹合併)
LOJ 洛谷 BZOJ 考慮\(l=1,r=|S|\)的情況: 對\(S\)串建SAM,\(T\)在上面匹配,可以得到每個位置\(i\)的字尾的最長匹配長度\(mx[i]\)。 因為要去重,對\(T\)也建SAM,計算上面所有節點的答案。記\(pos[i]\)表示\(i\)節點第一次出現的下標(同一節點代表
[LOJ#2473][九省聯考2018]祕密襲擊(樹形DP+生成函式+線段樹合併+拉格朗日插值)
Address 洛谷P4365 BZOJ5250 LOJ#2473 The First Step - 轉化 簡版題意:給定一棵點帶權樹,求樹上所有大小大於 k
codeforcs 1063F. String Journey - dp -SAM - 主席樹/線段樹合併 - 子串定位 - 倍增
題目大意: 給你一個長為 n n n的字串
洛谷P3066 [USACO12DEC]逃跑的Barn (線段樹合併)
題目描述It's milking time at Farmer John's farm, but the cows have all run away! Farmer John needs to round them all up, and needs your help in the search.
bzoj 4756 [Usaco2017 Jan]Promotion Counting——線段樹合併
題目:https://www.lydsy.com/JudgeOnline/problem.php?id=4756 線段樹合併裸題。那種返回 int 的與傳引用的 merge 都能過。不知別的題是不是這樣。 #include<iostream> #include<cstdio>
BZOJ:5457: 城市(線段樹合併)(尚待優化)
5457: 城市 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 18 Solved: 12[Submit][Status][Discuss] Description 有n
[學習筆記]線段樹合併
1、[POI2011]ROT-Tree Rotations 分析:線段樹合併人生第一題。 網上的題解我都沒看懂……我自己講一下好了 線段樹合併就是把兩棵權值線段樹合併到一棵 那怎麼合併呢? 假設有這麼兩棵樹: 一個結點代表一段值域區間有幾個數,那麼可以看出合併後應該是這樣的 然後具體
Gym - 101194G Pandaria (並查集+倍增+線段樹合併)
題意: 給定一個無向圖。每個點有一種顏色。現在給定q個詢問,每次詢問x和w,求所有能通過邊權值不超過w的邊走到x的點的集合中,哪一種顏色的點出現的次數最多。次數相同時輸出編號最小的那個顏色。強制線上。 解題思路:膜拜大神們的程式碼!看了好久,終於
LOJ2537 PKUWC2018 Minimax 樹形DP、線段樹合併
傳送門 題意:自己去看 首先可以知道,每一個點都有機率被選到,所以$i$與$V_i$的關係是確定了的。 所以我們只需要考慮每一個值的取到的概率。 很容易設計出一個$DP$:設$f_{i,j}$為在第$i$個點取到權值第$j$小的點的概率,轉移就是$f_{i,j}=f_{lson,j} \times
BZOJ4919 大根堆(動態規劃+線段樹合併/treap+啟發式合併)
一個顯然的dp是設f[i][j]為i子樹內權值<=j時的答案,則f[i][j]=Σf[son][j],f[i][a[i]~n]++。這樣是可以線段樹合併的,將各兒子加起來然後打上加法標記即可。需要標記永久化。 另一種做法是考慮擴充套件經典的單調佇列優化LIS的做法,維護子樹內答案為k時最小的最
Loj2537 「PKUWC2018」Minimax (線段樹合併維護dp)
分析 設 f [ i ]