realme 真我 GT 大師探索版開啟 realme UI 3.0 嚐鮮版(基於安卓 12)報名
阿新 • • 發佈:2021-11-11
前言
【題目傳送門】
小清新題目,很放鬆身心。
用時:\(20min\)
題解
樹狀陣列
逆序對先想到樹狀陣列。
大概想一想,發現從子樹向父親回溯的過程也滿足逆序對的加入順序關係。
再想想,發現不同子樹內的點這樣統計會出錯,因為每個點只和子樹內的點產生的逆序對有關。
也不難解決,進入這個點之前算一遍逆序對,走完子樹後算一遍逆序對,兩次的差值就是子樹內的貢獻啦。
程式碼也很小清新。
程式碼
#include<bits/stdc++.h> using namespace std; #define ll long long #define FCC fclose(stdin),fclose(stdout) const int INF = 0x3f3f3f3f,N = 1e5+10; inline ll read() { ll ret=0;char ch=' ',c=getchar(); while(!(c>='0'&&c<='9')) ch=c,c=getchar(); while(c>='0'&&c<='9') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar(); return ch=='-'?-ret:ret; } int n,a[N],p[N],tot; int head[N],ecnt=-1; int ans[N]; void init_edge(){memset(head,-1,sizeof(head)),ecnt=-1;} struct edge { int nxt,to; }e[N<<1]; inline void add_edge(int x,int y) { e[++ecnt]=(edge){head[x],y}; head[x]=ecnt; } struct BIT { int c[N]; inline void update(int x,int v){for(int i=x;i<=tot;i+=i&-i) c[i]+=v;} inline int sum(int x) { int ret=0; for(int i=x;i;i-=i&-i) ret+=c[i]; return ret; } inline int query(int l,int r){return sum(r)-sum(l-1);} }tr; void discrete() { sort(p+1,p+n+1); tot=unique(p+1,p+n+1)-p-1; for(int i=1;i<=n;i++) a[i]=lower_bound(p+1,p+tot+1,a[i])-p; return; } void dfs(int u,int fa) { int pre=tr.query(a[u]+1,tot); for(int i=head[u];~i;i=e[i].nxt) { int v=e[i].to; if(v==fa) continue; dfs(v,u); tr.update(a[v],1); } int now=tr.query(a[u]+1,tot); ans[u]=now-pre; } int main() { init_edge(); n=read(); for(int i=1;i<=n;i++) a[i]=p[i]=read(); discrete(); for(int i=2;i<=n;i++) { int u=read(); add_edge(u,i),add_edge(i,u); } dfs(1,-1); for(int i=1;i<=n;i++) printf("%d\n",ans[i]); return 0; }
線段樹合併
明顯也可以做,甚至看起來比模板還簡單。
仍然對每一個點開一顆權值線段樹,回溯的時候合併資訊即可。
等下再貼程式碼。
樹上啟發式合併
顯然也可做。
加深一下對書上啟發式合併的理解吧,重申一下概念:
對於每一個節點 \(u\) 計算所有子樹 \(v\) 內的貢獻。但是為了防止不同的 \(v\) 子樹之間互相干擾,所以每次走完一個 \(v\) 就要把他的貢獻清除。但是當走到最後一個 \(v\) 時,它後面沒有兄弟了,就不需要清除貢獻,可以直接傳遞給父親 \(u\),這個最後的 \(v\) 就是 \(hson_u\)(即重兒子)。