启发式合并还是太万能了点

求今年NOIP靠树上启发式合并qaq。

https://www.luogu.com.cn/problem/P4556

正解(大概)线段树合并。

考虑类似扫描线式地树上差分。求贡献考虑使用树上启发式合并+set来维护。

d[x] += 1, d[y] += 1, d[lca] -= 1, d[fa(lca)] -= 1

d[x] += 1, d[y] += 1, d[lca] -= 2

优秀的细节

删点贡献和加点贡献考虑使用dfn便历法代替dfs。

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N = 1e5+11 ;
int f[N][20],dep[N],ans[N],son[N];
int sz[N],L[N],R[N],dfn[N],Node[N],tot,cnt[N];
vector<int> g[N],ad[N],de[N];
struct kkk{
	int id,v;
	bool operator < (const kkk a)const{
		if(a.v == v)return id<a.id;
		return v>a.v; 
	}
};
set<kkk> s;
void init(int u,int fa){
	tot++; 
	L[u] = tot;
	Node[tot] = u;
	f[u][0] = fa;
	sz[u] = 1;
	dep[u] = dep[fa]+1;
	for(int i = 1;i <= 18;i++){
		f[u][i] = f[f[u][i-1]][i-1];
	}
	for(int v:g[u]){
		if(v == fa)continue;
		init(v,u);
		sz[u]+=sz[v];
		if(sz[v]>sz[son[u]]){
			son[u]=v;
		}
	}
	R[u] = tot;
}
int lca(int u,int v){
	if(dep[v]>dep[u])swap(u,v);
	for(int i = 18;i>=0;i--){
		if(dep[f[u][i]] >= dep[v]){
			u = f[u][i];
		}
	}
	if(u == v)return v;
	for(int i = 18;i >= 0;i--){
		if(f[u][i]!=f[v][i]){
			u = f[u][i];
			v = f[v][i];
		}
	}
	return f[u][0];
}
void add(int u) {
    for(int col:ad[u]){
    	if(cnt[col]>0)s.erase({col,cnt[col]});
    	cnt[col]++;
    	s.insert({col,cnt[col]});
    	
	}
	for(int col:de[u]){
		s.erase({col,cnt[col]});
		cnt[col]--;
		if(cnt[col]>0)s.insert({col,cnt[col]});
	}
}
void del(int u){
	for(int col:ad[u]){
		s.erase({col,cnt[col]});
		cnt[col]--;
		if(cnt[col]>0)s.insert({col,cnt[col]});
	}
	for(int col:de[u]){
		if(cnt[col]>0)s.erase({col,cnt[col]});
    	cnt[col]++;
    	s.insert({col,cnt[col]});
	}
}
void dfs(int u,int fa,bool keep){
	for(int v:g[u]){
		if(v == fa||v == son[u])continue;
		dfs(v,u,0);
	}
	if(son[u]){
		dfs(son[u],u, 1);
	}
	for(int v:g[u]){
		if(v == fa||v == son[u])continue;
		for (int i = L[v]; i <= R[v]; i++) {
        	add(Node[i]);
      	}
	}
	add(u);
	ans[u] = (*s.begin()).id;
	
	
	if (!keep) {
    	for (int i = L[u]; i <= R[u]; i++) {
      		del(Node[i]);
    	}
  	}
}
int main(){
	ios::sync_with_stdio(0);
	cin>>n>>m;
	for(int i = 1;i <= n-1;i++){
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	init(1,0);
	s.insert({0,0});
	for(int i = 1;i <= m;i++){
		int x,y,z;
		cin>>x>>y>>z;
		ad[x].push_back(z);
		ad[y].push_back(z);
		de[lca(x,y)].push_back(z);
		de[f[lca(x,y)][0]].push_back(z);
		//cout<<lca(x,y)<<endl;
	}
	dfs(1,0,0);
	for(int i = 1;i <= n;i++){
		cout<<ans[i]<<endl;
	}
	return 0;
} 
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值