1. 程式人生 > >BZOJ2212 [Poi2011]Tree Rotations 【線段樹合並】

BZOJ2212 [Poi2011]Tree Rotations 【線段樹合並】

namespace 二叉 情況 一點 sizeof mem IV efi ota

題目鏈接

BZOJ2212

題解

一棵子樹內的順序不影響其與其它子樹合並時的答案,這一點與歸並排序的思想非常相似
所以我們只需單獨處理每個節點的兩棵子樹所產生的最少逆序對即可
只有兩種情況,要麽正序要麽逆序,且這兩種情況數目是互補的
如果左子樹大小為\(S_l\),右子樹大小為\(S_r\),那麽總對數為\(S_lS_r\)

如何快速統計一棵子樹中大於另一棵子樹中權值的對數?
開一個權值線段樹,在線段樹合並過程中統計即可
由於權值是一個排列,所以復雜度是\(O(nlogn)\)

順帶一提,左右兒子都滿的二叉樹節點數\(n = 2s - 1\)\(s\)表示葉子的個數
可以用數歸證明
所以節點數最多不超過\(4 \times 10^5\)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map>
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define mp(a,b) make_pair<int,int>(a,b)
#define cls(s) memset(s,0,sizeof(s))
#define cp pair<int,int> #define LL long long int using namespace std; const int maxn = 400005,maxm = 10000005,INF = 1000000000; inline int read(){ int out = 0,flag = 1; char c = getchar(); while (c < 48 || c > 57){if (c == ‘-‘) flag = -1; c = getchar();} while (c >= 48 && c <= 57
){out = (out << 3) + (out << 1) + c - 48; c = getchar();} return out * flag; } int n,N,rt[maxn],sum[maxm],ls[maxm],rs[maxm],cnt; LL ans,tot; void modify(int& u,int pre,int l,int r,int pos){ sum[u = ++cnt] = sum[pre] + 1; ls[u] = ls[pre]; rs[u] = rs[pre]; if (l == r) return; int mid = l + r >> 1; if (mid >= pos) modify(ls[u],ls[pre],l,mid,pos); else modify(rs[u],rs[pre],mid + 1,r,pos); } int merge(int u,int v){ if (!u) return v; if (!v) return u; int t = ++cnt; sum[t] = sum[u] + sum[v]; tot += 1ll * sum[rs[u]] * sum[ls[v]]; ls[t] = merge(ls[u],ls[v]); rs[t] = merge(rs[u],rs[v]); return t; } int dfs(){ int x = read(); int u = ++N; if (x){ modify(rt[u],rt[u],1,n,x); return u; } int L = dfs(),R = dfs(); LL S = 1ll * sum[rt[L]] * sum[rt[R]]; tot = 0; rt[u] = merge(rt[L],rt[R]); ans += min(tot,S - tot); return u; } int main(){ n = read(); dfs(); printf("%lld\n",ans); return 0; }

BZOJ2212 [Poi2011]Tree Rotations 【線段樹合並】