点分治与dsu学习笔记

前言

又想起来在西安学了但回来就忘了学过了的东西。
到了这里我似乎只剩多项式没写了吧。

点分治

【模板】点分治 1

我们将树上路径分为两类,一类为经过根节点的,一类为不经过根节点的。
同时,我们定义 \(dis_x\) 表示从 \(x\) 出发到根节点的路径长。
那么对于第一类路径,有两点 \((x,y)\) 间距离为:\(dis_x+dis_y\)
对于第二类路径,将两路径的 LCA 为根,直接转化成第一类路径。
这就是点分治思想,简单而言,将一棵树划分为许多子树,然后依次求解。
复杂度分析:对于每一次求 \(dis\) ,是 \(O(n)\) 的,所以复杂度是 \(O(T\times n)\)\(T\) 为递归层数。
我们并不希望 \(T\) 过于劣,所以我们定的根需要是重心,这样的复杂度是 \(O(n\log n)\) 的,否则最劣为 \(O(n^2)\)
code:

#include<iostream>

using namespace std;
const int N=1e5+50;
const int M=2e7+50;
struct Edge{
	int to,nxt,w;
}e[2*N];
int head[N],tot;
void add(int u,int v,int w){
	e[++tot].to=v;
	e[tot].nxt=head[u];
	e[tot].w=w;
	head[u]=tot;
	return ;
}
int rt,maxp[N],siz[N],dis[N],rem[N];
int vis[N],cnt,judge[M],q[N],res[N];
int query[1010];
int sum,ans;
int n,m;
void grt(int u,int pa){
	siz[u]=1;maxp[u]=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==pa || vis[v]) continue;
		grt(v,u);
		siz[u]+=siz[v];
		maxp[u]=max(maxp[u],siz[v]);
	}
	maxp[u]=max(maxp[u],sum-siz[u]);
	if(maxp[u]<maxp[rt]) rt=u;
	return ;
}
void gdis(int u,int fa){
	rem[++cnt]=dis[u];
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa || vis[v]) continue;
		dis[v]=dis[u]+e[i].w;
		gdis(v,u);
	}
	return ;
}
void calc(int u){
	int p=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(vis[v]) continue;
		cnt=0;
		dis[v]=e[i].w;
		gdis(v,u);
		for(int j=cnt;j>=0;j--){
			for(int k=1;k<=m;k++){
				if(query[k]>=rem[j]){
					res[k]|=judge[query[k]-rem[j]];
				}
			}
		}
		for(int j=cnt;j>=0;j--){
			q[++p]=rem[j],judge[rem[j]]=1;
		}
	}
	for(int i=1;i<=p;i++){
		judge[q[i]]=0;
	}
	return ;
}
void solve(int u){
	vis[u]=judge[0]=1;
	calc(u);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(vis[v]) continue;
		sum=siz[v],maxp[rt=0]=M;
		grt(v,0);
		solve(rt);
	}
}
int main(){
	cin>>n>>m;
	for(int i=1;i<n;i++){
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w);
		add(v,u,w);
	}
	for(int i=1;i<=m;i++){
		cin>>query[i];
	}
	maxp[rt]=sum=n;
	grt(1,0);
	solve(rt);
	for(int i=1;i<=m;i++){
		if(res[i]) cout<<"AYE"<<'\n';
		else cout<<"NAY"<<'\n';
	}
	return 0;
}

树上启发式合并(dsu on tree)

只能处理离线问题,是个离线算法。
只有对子树的询问。

Lomsat gelral

板子题

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

可以发现,每个节点的答案其实由节点的子树决定,所以可以将子树的贡献算完后直接给父亲。
但是,父亲的答案并不由一棵子树单独决定,所以每次计算完后还需要把贡献撤销。
但是,最后一棵计算的子树不用将贡献撤销了。
令最后一棵子树是重儿子,而后的算法流程如下:

  1. 递归所有轻儿子,并消除递归计算时产生的贡献。
  2. 递归重儿子,不消除递归计算时产生的贡献。
  3. 统计轻儿子对答案的贡献。
  4. 更新答案。
  5. 消除轻儿子对答案的贡献。

code:

#include<iostream>
#define int long long
using namespace std;
const int N=1e5+50;
int n,c[N];
struct Edge{
	int to,nxt;
}e[2*N];
int head[N],tot;
void add(int u,int v){
	e[++tot].to=v;
	e[tot].nxt=head[u];
	head[u]=tot;
	return ;
}
int siz[N],hson[N];
int cnt[N];
int mx,res,ans[N],son;
void dfs1(int u,int f){
	siz[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==f) continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[hson[u]]<siz[v]){
			hson[u]=v;
		}
	}
	return ;
}
void calc(int u,int f,int val){
	cnt[c[u]]+=val;
	if(cnt[c[u]]>mx) mx=cnt[c[u]],res=c[u];
	else if(cnt[c[u]]==mx) res+=c[u];
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==f || v==son) continue;
		calc(v,u,val);
	}
	return ;
}
void dfs(int u,int f,bool keep){
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==f || v==hson[u]) continue;
		dfs(v,u,0);
	}
	if(hson[u]) dfs(hson[u],u,1),son=hson[u];
	calc(u,f,1);
	son=0;
	ans[u]=res;
	if(!keep) calc(u,f,-1),res=0,mx=0;
	return ;
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>c[i];
	}
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	dfs1(1,0);
	dfs(1,0,1);
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<' ';
	}
	return 0;
}
posted @ 2025-04-19 17:30  Tighnari  阅读(9)  评论(0)    收藏  举报