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

线段树合并差分

链接:P4556 [Vani有约会]雨天的尾巴 /【模板】线段树合并 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)****
题意:给定一棵树,多次修改将 x 到 y 的路径上的点均加上 z 号颜色,最后求出每个点的最多出现的颜色。出现颜色次数一样时取编号最小的那个。

题解:多次操作后最后再求答案,那么其实操作就不需要实时查询了,可以采用差分的形式在 x 到 y 的链上打标记,最后在求差分的前缀和即为答案。不一样的是,差分和求前缀和均是在线段树上通过更新,合并来实现的。对于 x 到 y 路径上的点,只需要求出 lca ,在 x,y 处分别打上加入 z 的记号,在 lca 及其父节点上打上 -z 的记号,然后计算子树时直接更新合并即可。注意可能有点是不存在任意颜色的,输出 0 。当所有颜色均出现却没有在某个点上时,可能会出现将区间内 [1,m] 内输出 1 。其实应该是 0 。所以更新区间应该为 [0,m] 。保证出现次数为 0 时编号最小的是 0 。

// #pragma GCC optimize("Ofast")
// #pragma GCC optimize("unroll-loops")
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<functional>
#include<queue>
#include<unordered_map>
#include<map>
#include<set>

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;
	Merge(int x=N):n((x+5)*50),t(n),ls(n),rs(n),mx(n),va(n),rt(x+5),now(0){}

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

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

	void merge(int&u,int&v,int l,int r){
		if(!u||!v){u=u|v; return;}
		if(l==r)
		{
			va[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);
	}

}tr;

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

	vector<int>ans(n+1);
	vector<vector<P>>ch(n+1);

	vector<int>dep(n+1),lg(n+1);
	vector<vector<int>>f(n+1,vector<int>(20));

	for(int i=1;i<=n;i++)lg[i]=lg[i-1]+(!(i&i-1));

	auto dfs=[&](auto dfs,int x,int fa)->void{
		dep[x]=dep[fa]+1,f[x][0]=fa;
		for(int i=1;i<20;i++)
		{
			f[x][i]=f[f[x][i-1]][i-1];
		}
		for(auto y:ed[x])
		{
			if(y==fa)continue;
			dfs(dfs,y,x);
		}
	};

	auto lca=[&](int x,int y){
		if(dep[x]<dep[y])swap(x,y);
		while(dep[x]>dep[y])
		{
			x=f[x][lg[dep[x]-dep[y]]-1];
		}
		if(x==y)return x;
		for(int i=19;i>=0;i--)
		{
			if(f[x][i]!=f[y][i])
			{
				x=f[x][i],y=f[y][i];
			}
		}
		return f[x][0];
	};

	dfs(dfs,1,0);
	int mx=0;
	for(int i=1,u,v,z,w;i<=m;i++)
	{
		cin>>u>>v>>z;
		w=lca(u,v);
		ch[u].push_back({z,1});
		ch[v].push_back({z,1});
		ch[w].push_back({z,-1});
		ch[f[w][0]].push_back({z,-1});
		mx=max(mx,z);
	}

	auto dfs1=[&](auto dfs1,int x,int fa)->void{
		for(auto[a,b]:ch[x])
		{
			tr.update(tr.rt[x],0,mx,a,b);
		}
		for(auto y:ed[x])
		{
			if(y==fa)continue;
			dfs1(dfs1,y,x);
			tr.merge(tr.rt[x],tr.rt[y],0,mx);
		}
		ans[x]=tr.va[tr.rt[x]];
	};

	dfs1(dfs1,1,0);
	for(int i=1;i<=n;i++)
	{
		cout<<ans[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;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值