1. 程式人生 > 資訊 >realme 真我 GT 大師探索版開啟 realme UI 3.0 嚐鮮版(基於安卓 12)報名

realme 真我 GT 大師探索版開啟 realme UI 3.0 嚐鮮版(基於安卓 12)報名

前言

【題目傳送門】
小清新題目,很放鬆身心。
用時:\(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\)(即重兒子)。