【bzoj3702】二叉樹 權值線段樹
阿新 • • 發佈:2019-02-15
神奇的解法
對於每個節點,建出權值線段樹
每次查詢右子樹的權值線段樹和左子樹的權值線段樹,左子樹中比右子樹小的有多少?右子樹比左子樹小的有多少?(分別對應不交換的逆序對和交換的逆序對)
將左子樹和右子樹的權值線段樹合併
遞迴進行這個操作
感覺複雜度很不靠譜,於是想證明一下複雜度
最開始權值線段樹共O(nlogn)個節點,最後共O(n)個節點
每次合併兩棵樹的每個節點都要訪問一遍,所以每個節點好像是要訪問O(dep[i])次?
但是,合併兩棵樹後,有些重複的節點被合併到了一起
所以好像有些節點又沒有合併O(dep[i])次?
對於每個節點,建出權值線段樹
每次查詢右子樹的權值線段樹和左子樹的權值線段樹,左子樹中比右子樹小的有多少?右子樹比左子樹小的有多少?(分別對應不交換的逆序對和交換的逆序對)
將左子樹和右子樹的權值線段樹合併
遞迴進行這個操作
感覺複雜度很不靠譜,於是想證明一下複雜度
最開始權值線段樹共O(nlogn)個節點,最後共O(n)個節點
每次合併兩棵樹的每個節點都要訪問一遍,所以每個節點好像是要訪問O(dep[i])次?
但是,合併兩棵樹後,有些重複的節點被合併到了一起
所以好像有些節點又沒有合併O(dep[i])次?
感覺很靠譜,但是不會證明
#include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<iostream> #include<algorithm> #define maxn 400010 #define N 4000100 using namespace std; int lch[N],rch[N],sum[N]; int root[maxn],l[maxn],r[maxn],w[maxn]; int n,m,num,tot; long long ans,cnt1,cnt2; void dfs(int x) { scanf("%d",&w[x]); if (!w[x]) { l[x]=++num; dfs(num); r[x]=++num; dfs(num); } } void update(int x) { sum[x]=sum[lch[x]]+sum[rch[x]]; } void modify(int &x,int l,int r,int d) { if (!x) x=++tot; if (l==r) {sum[x]=1;return;} int mid=(l+r)/2; if (d<=mid) modify(lch[x],l,mid,d); else modify(rch[x],mid+1,r,d); update(x); } int merge(int x,int y) { if (!x) return y; if (!y) return x; cnt1+=(long long)sum[rch[y]]*sum[lch[x]]; cnt2+=(long long)sum[rch[x]]*sum[lch[y]]; lch[x]=merge(lch[x],lch[y]); rch[x]=merge(rch[x],rch[y]); update(x); return x; } void dfs1(int x) { if (!w[x]) { dfs1(l[x]); dfs1(r[x]); cnt1=cnt2=0; root[x]=merge(root[l[x]],root[r[x]]); ans+=min(cnt1,cnt2); } } int main() { scanf("%d",&n); num++;dfs(1); for (int i=1;i<=num;i++) if (w[i]) modify(root[i],1,n,w[i]); dfs1(1); printf("%lld\n",ans); return 0; }