线段树合并经典例题(1)

最大出现次数的数字和

链接:CF600E Lomsat gelral - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意:

  • 有一棵 n 个结点的以 1 号结点为根的有根树
  • 每个结点都有一个颜色,颜色是以编号表示的, i 号结点的颜色编号为 c i c_i ci
  • 如果一种颜色在以 x 为根的子树内出现次数最多,称其在以 x 为根的子树中占主导地位。显然,同一子树中可能有多种颜色占主导地位。
  • 你的任务是对于每一个 i ∈ [ 1 , n ] i∈[1,n] i[1,n],求出以 i 为根的子树中,占主导地位的颜色的编号和。
  • n ≤ 1 0 5 , c i ≤ n ≤ 1 0 5 n\le 10^5,c_i\le n≤10^5 n105,cin105

题解:对于任意一个点都要求出整个子树内最多出现次数的数字和,可以使用启发式式合并来解决这道题,当然也可以使用线段树合并来解决。

前置芝士

动态开点,权值线段树

线段树合并

线段树合并主要是将两棵动态开点的权值线段树进行合并。假设当前合并的线段树为左,右线段树,最后将右线段树合并到左线段树上。合并方式是左线段树或者右线段树为空树时,直接将两棵树的或值给左线段树即可。当两棵树均为非空时,说明都需要继续按左右子树递归合并,直到叶子节点时可以直接赋值结束。虽然看着很不靠谱,但复杂度是 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n)) 的。

实现

了解了线段树合并之后,容易发现对于一个父节点是可以将子节点的线段树直接合并上来的,那么可以先计算子节点再计算父节点。对于线段树上维护的,应该是最多出现次数,及其和。因为是权值线段树,则合并时底层各个权值的出现次数已经累加完,要计算的是最多出现次数及其和。当线段树上的左右子树最大出现次数不一样时,取大的那边,一样时则可以把数字和累加,最后每个节点的答案即为该点线段树头节点的数字和。

#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#include
#include
#include
#include
#include
#include
#include
#include
#include

using namespace std;
using ll=long long;
using P=pair<int,int>;
const ll inf=1e18;

struct Merge{
	static constexpr int N=1e5+5;
	int n,now;
	vector<int>t,ls,rs,mx,va,rt;
	vector<ll>sm;
	Merge(int x=N):n(x+5<<5),t(n),ls(n),rs(n),mx(n),sm(n),va(n),rt(x+5),now(0){}

	void pushup(int k){
		int l=ls[k],r=rs[k];
		if(mx[l]>mx[r])
		{
			sm[k]=sm[l];
			mx[k]=mx[l];
			va[k]=va[l];
		}  
		else if(mx[l]<mx[r])
		{
			sm[k]=sm[r];
			mx[k]=mx[r];
			va[k]=va[r];
		}
		else
		{
			sm[k]=sm[l]+sm[r];
			mx[k]=mx[l];
			va[k]=va[l];
		}
	}

	void update(int&u,int l,int r,int pos){
		if(!u)u=++now;
		if(l==r)
		{
			sm[u]+=l;
			mx[u]++;
			va[u]=l;
			return;
		}
		int mid=l+r>>1;
		if(pos<=mid)update(ls[u],l,mid,pos);
		else update(rs[u],mid+1,r,pos);
		pushup(u);
	}

	void merge(int&u,int&v,int l,int r){
		if(!u||!v){u=u|v; return;}
		if(l==r)
		{
			va[u]=sm[u]=l;
			mx[u]+=mx[v];
			return;
		}
		int mid=l+r>>1;
		merge(ls[u],ls[v],l,mid);
		merge(rs[u],rs[v],mid+1,r);
		pushup(u);
	}

};

void solve()
{
	int n; cin>>n;
	vector<int>a(n+1);
	vector<vector<int>>ed(n+1);
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1,u,v;i<n;i++)
	{
		cin>>u>>v;
		ed[u].push_back(v);
		ed[v].push_back(u);
	}

	Merge tr(n);
	vector<ll>ans(n+1);

	auto dfs=[&](auto dfs,int x,int fa)->void{
		tr.update(tr.rt[x],1,n,a[x]);
		for(auto y:ed[x])
		{
			if(y==fa)continue;
			dfs(dfs,y,x);
			tr.merge(tr.rt[x],tr.rt[y],1,n);
		}
		ans[x]=tr.sm[tr.rt[x]];
	};

	dfs(dfs,1,0);
	for(int i=1;i<=n;i++)
	{
		cout<<ans[i]<<" \n"[i==n];
	}

}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	int t=1; //cin>>t;
	while(t--)solve();
	return 0;
} 

你可能感兴趣的:(线段树,c++,算法,数据结构)