树上数据结构

树上问题

树链剖分学习笔记

重链剖分

对树进行重链优先搜索,暴力求一条路径的复杂度为logn

模板

int siz[MAXN],f[MAXN],hson[MAXN],deep[MAXN],top[MAXN],dfn[MAXN],rdfn[MAXN],rak[MAXN],cnt;
void tree_build(int u,int fa) {//重链优先搜索
	siz[u]=1;
	f[u]=fa;
	hson[u]=0;
	for(auto &v:adj[u]) {
		if(v==fa) continue;
        deep[v]=deep[u]+1;
		tree_build(v,u);
		siz[u]+=siz[v];
		if(hson[u]==0||siz[v]>siz[hson[u]]) hson[u]=v;
	}
}
void tree_decom(int u,int t) {//dfn序
	top[u]=t;
	cnt++;
	dfn[u]=cnt;
	rak[cnt]=u;
	if(hson[u]!=0) {
		tree_decom(hson[u],t);
		for(auto &v:adj[u]) {
			if(hson[u]!=v&&v!=f[u]) tree_decom(v,v);
		}
	}
	rdfn[u]=cnt;
}

求LCA

int getLCA(int u,int v) {
	while(top[v]!=top[u]) {
		if(deep[top[u]]>deep[top[v]]) u=f[top[u]];
		else v=f[top[v]];
	}
	return deep[u]>deep[v]?v:u;
}

模板题1 洛谷模板

1690426592157.png

代码

while(m--) {
		int op;
		cin>>op;
		if(op==1) {
			int u,v,w;
			cin>>u>>v>>w;
			while(top[v]!=top[u]) {
				if(deep[top[u]]>deep[top[v]]) {
					modify(1,1,n,dfn[top[u]],dfn[u],w);
					u=f[top[u]];
				} else {
					modify(1,1,n,dfn[top[v]],dfn[v],w);
					v=f[top[v]];
				}
			}
			if(dfn[v]<dfn[u]) swap(u,v);
			modify(1,1,n,dfn[u],dfn[v],w);
		} else if(op==2) {
			int u,v;
			cin>>u>>v;
			int ans=0;
			while(top[v]!=top[u]) {
				if(deep[top[u]]>deep[top[v]]) {
					ans+=query(1,1,n,dfn[top[u]],dfn[u]).sum;
					u=f[top[u]];
				} else {
					ans+=query(1,1,n,dfn[top[v]],dfn[v]).sum;
					v=f[top[v]];
				}
			}
			if(dfn[v]<dfn[u]) swap(u,v);
			ans+=query(1,1,n,dfn[u],dfn[v]).sum;
			cout<<ans%p<<"\n";
		} else if(op==3) {
			int u,w;
			cin>>u>>w;
			modify(1,1,n,dfn[u],rdfn[u],w);
		} else if(op==4) {
			int u;
			cin>>u;
			int ans=query(1,1,n,dfn[u],rdfn[u]).sum;
			cout<<ans%p<<"\n";
		}
	}

模板题2 Omsk Metro (hard version)

Problem - 1843F2 - Codeforces

1690426949862.png

思路

1690427002246.png

代码

//线段树
Info operator + (const Info &a,const Info &b){
    Info c;
    c.sum=a.sum+b.sum;
    c.lmin=min(a.lmin,a.sum+b.lmin);
    c.lmax=max(a.lmax,a.sum+b.lmax);
    c.rmin=min(b.rmin,b.sum+a.rmin);
    c.rmax=max(b.rmax,b.sum+a.rmax);
    c.min=min({a.min,a.rmin+b.lmin,b.min});
    c.max=max({a.max,a.rmax+b.lmax,b.max});
    return c  ;
}
//main函数
for(auto it:que){
        int u=it.first.first;
        int v=it.first.second;
        int value=it.second;
        Info answer;//ans是最后的信息合并 
        if(deep[u]<deep[v]) swap(u,v);
        Info left,right;
        while(top[u]!=top[v]){//跳重链 
            if(deep[top[u]]>deep[top[v]]){
                left=query(1,min(dfn[top[u]],dfn[u]),max(dfn[top[u]],dfn[u]))+left;
                u=f[top[u]];
            }
            else{
                right=query(1,min(dfn[top[v]],dfn[v]),max(dfn[top[v]],dfn[v]))+right;
                v=f[top[v]];
            }
        }
        if(dfn[u]<=dfn[v])
        right=query(1,min(dfn[u],dfn[v]),max(dfn[u],dfn[v]))+right;
        else 
        left=query(1,min(dfn[u],dfn[v]),max(dfn[u],dfn[v]))+left;
        answer = rev(left)+right;
        if(value>=answer.min&&value<=answer.max) cout<<"YES\n";
        else cout<<"NO\n";
    } 

长链剖分

树上启发式合并学习笔记

模板

优化合并复杂度的方法

重链剖分

复杂度为logn

void tree_build(int u,int fa){
	siz[u]=1;
	hson[u]=0;
	for(auto &v:adj[u]){
		if(v==fa) continue;
		dep[v]=dep[u]+1;
		tree_build(v,u);
		siz[u]+=siz[v];
		if(siz[v]>siz[hson[u]])hson[u]=v;
	}
}

长链剖分

复杂度为根号n 用于优化树上dp

void tree_build(int u,int fa){
	int maxdep=0;
	deep[u]=1;
	for(auto &v:adj[u]){
		if(v==fa) continue;
		f[v]=u;
		tree_build(v,u);
		if(deep[v]>maxdep){
			maxdep=deep[v];
			lson[u]=v;
		}
		deep[u]=max(deep[u],deep[v]+1);
	}
}

合并信息模板

void calc(int u,int fa,int val) { //统计答案
	if(val==1){//表示在增加这个节点时需要增加的操作
        
    }
    else{//表示在去掉这个节点时需要消去的操作
        
    }
	for(auto &v:adj[u]) {
		if(v==fa||v==HH) continue;
		calc(v,u,val);
	}
}
void dsu(int u,int fa,int op){
    for(auto &v:adj[u]){
        if(v==fa||v==hson[u]) continue;
        dsu(v,u,0);
    }
    if(hson[u]){
        dsu(hson[u],u,1),
        HH=hson[u];
    }
    calc(u,fa,1);
    /*在这里对答案进行统计 一般每个dsu(u)的答案就是该节点的答案
    */
	HH=0;
    if(!op){calc(u,fa,-1);
    }
}

题目选

题1 Lomsat gelral

寻找子树最大颜色的和

  • 1690421863520.png
void calc(int u,int fa,int val) { //统计答案
	if(val==1){
		cntc[c[u]]++;//增加颜色操作
		if(cntc[c[u]]>ma){
			ma=cntc[c[u]];
			res=c[u];
		}
		else if(cntc[c[u]]==ma)
			res+=c[u];
		}
	}
		
	else{
		cntc[c[u]]--;//减少颜色
		if(cntc[c[u]]>ma){
			ma=cntc[c[u]];
			res=c[u];
		}
		else if(cntc[c[u]]==ma){
			res+=c[u];
		}
	}
	//增加或者减少后,统计新的值
	for(auto &v:adj[u]) {
		if(v==fa||v==HH) continue;
		calc(v,u,val);
	}
}
void dsu(int u,int fa,int op) { //op=1 传递 否则保留
	for(auto &v:adj[u]) {
		if(v==fa||v==hson[u]) continue;
		dsu(v,u,0);//先遍历轻儿子
	}
	if(hson[u]) dsu(hson[u],u,1),HH=hson[u];
	calc(u,fa,1);
	ans[u]=res; //统计每个点答案
	HH=0;
	if(!op) calc(u,fa,-1),res=ma=0;//表示不用传递,要把res清空
}

题2 Blood Cousins Return

寻找子树每一层不同颜色的个数

用set去统计对于每个dsu到的点的节点信息 set表示离根节点距离的名字

1690422390485.png

void calc(int u,int fa,int val) { //统计答案
	if(val==1){
        v[dep[u]].insert(s[u]);//加入节点
    }
    else{
        v[dep[u]].erase(s[u]);
    }
	for(auto &v:adj[u]) {
		if(v==fa||v==HH) continue;
		calc(v,u,val);
	}
}
void dsu(int u,int fa,int op){
    for(auto &v:adj[u]){
        if(v==fa||v==hson[u]) continue;
        dsu(v,u,0);
    }
    if(hson[u]){
        dsu(hson[u],u,1),
        HH=hson[u];
    }
    calc(u,fa,1);
    for(auto &it:Q[u]){
    	int h=it[0],id=it[1];
    	ans[id]=v[dep[u]+h].size();
	}
	HH=0;
    if(!op){calc(u,fa,-1);
    }
}
题3 彩色的树

处理子树内某个深度差内的节点信息

考虑把信息全塞set里面 在从下往上dsu的时候,传递的同时删除深度越出的节点

  • 1690422962675.png

void del(int x){//删除过多节点
    if(x>n) return;
    while(!deps[x].empty()){
        auto it=deps[x].begin();
        if(cntc[*it]==1) res--;
        cntc[*it]--;
        deps[x].erase(it);
    }
}
void calc(int u,int fa,int val,int lim,int Son) { //统计答案
	if(deep[u]>lim) return;
	if(val==1){
		if(cntc[c[u]]==0){
			res++;
		}	deps[deep[u]].insert(c[u]);
	}
	else {
		if(cntc[c[u]]==1){
			res--;
		}
		auto it=deps[deep[u]].find(c[u]);
			deps[deep[u]].erase(it);
	}
	cntc[c[u]]+=val; 
	for(auto &v:adj[u]) {
		if(v==fa||(val==1&&v==Son)) continue;
		calc(v,u,val,lim,Son);
	}
}
void dsu(int u,int fa,int op) { //op=1 传递 否则保留
	for(auto &v:adj[u]) {
		if(v==fa||v==hson[v]) continue;
		dsu(v,u,0);//先遍历轻儿子
	}
	if(hson[u]!=-1) dsu(hson[u],u,1);
	calc(u,fa,1,deep[u]+k,hson[u]);
	ans[u]=res;
	if(!op) calc(u,fa,-1,deep[u]+k,hson[u]);//表示不用传递
	else{ del(deep[u]+k);	
	}
}

点分治

模板

void dfs(int x, int fa, ll sum, ll mx){
    ll s = sum + a[x];
    ll mm = max(mx, a[x]);
    o.push_back({mm, s});
    all1.push_back({mm, s});
    vx.push_back(s);
    for(auto v : adj[x]){
        if(v == fa || del[v]) continue;
        dfs(v, x, s, mm);
    }
}
 
void calc(int x){
   //统计答案
}
void getroot(int x, int fa){
    sz[x] = 1;
    int max_part = 0;
    for(auto &v : adj[x]){
        if(v == fa || del[v]) continue;
        getroot(v, x);
        sz[x] += sz[v];
        max_part = max(max_part, sz[v]);
    }
    max_part = max(max_part, sum - sz[x]);
    if(max_part < tmp){
        tmp = max_part;
        root = x;
    }
}
void divide(int x){
    calc(x);
    del[x] = 1;
    for(auto &v : adj[x]){
        if(del[v]) continue;
        tmp = sum = sz[v];
        getroot(v, 0);
        divide(root);
    }
}

题目选

题1 聪聪可可
  • 1690991302253.png

代码

#include<bits/stdc++.h>
#define close std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
typedef long long ll;
const ll MAXN =  3e5+7;
const ll mod = 1e9+7;
const ll inf = 0x3f3f3f3f;
int tmp,sum,root,ans=0,del[MAXN],sz[MAXN];
int f[3];
vector<pair<int,int> > adj[MAXN];
void dfs(int u, int fa, ll D) {

	for(auto it : adj[u]) {
		int v=it.first;
		int w=it.second;
		if(v == fa || del[v]) continue;
		f[(D+w)%3]++;
		dfs(v, u, w+D);
	}
}

int  calc(int u,int D) {
	//统计答案
	memset(f,0,sizeof(f));
	f[D%3]++; 
	dfs(u,0,D);
	return f[0]*f[0]+f[1]*f[2]*2;
}
void getroot(int x, int fa) {
	sz[x] = 1;
	int max_part = 0;
	for(auto &it : adj[x]) {
		int v=it.first;
		int w=it.second;
		if(v == fa || del[v]) continue;
		getroot(v, x);
		sz[x] += sz[v];
		max_part = max(max_part, sz[v]);
	}
	max_part = max(max_part, sum - sz[x]);
	if(max_part < tmp) {
		tmp = max_part;
		root = x;
	}
}
void divide(int x) {
	ans+=calc(x,0);
	del[x] = 1;
	for(auto &it : adj[x]) {
		int v=it.first;
		int w=it.second;
		if(del[v]) continue;
		ans-=calc(v,w);
		tmp = sum = sz[v];
		getroot(v, 0);
		divide(root);
	}
}
void solve() {
	int n;
	cin>>n;
	for(int i=1; i<n; i++) {
		int u,v,w;
		cin>>u>>v>>w;
		adj[u].push_back({v,w});
		adj[v].push_back({u,w});
	}
	tmp=sum=n;
	getroot(1,0);
	getroot(root,0);
	divide(root);
//	ans+=n;
	int fm=n*n;
	int k=__gcd(ans,fm);
	cout<<ans/k<<"/"<<fm/k;
}
signed main() {
	solve();
}

虚树

在问题询问很少但是树很大的情况下

可以考虑建虚树,保留关键点和关键点的lca

模板


vector<int> G[MAXN];
vector<pair<int,int> > Q[MAXN];
int par[MAXN][20],dep[MAXN];
//par[u][i]代表点u的祖先中 深度为max(1,dep[u]-2^i)是谁
void dfs(int u,int fa) {
	dep[u]=dep[fa]+1;
	par[u][0]=fa;
	for(int i=1; i<20; ++i) {
		par[u][i]=par[par[u][i-1]][i-1];
	}
	for(auto &v:G[u]) {
		if(v==fa) continue;
		dfs(v,u);
	}
}
void dfs2(int u,int fa) {
	d[u]=d[fa]+1;
	f[u]=fa;
	for(auto &v:vg[u]) {
		if(v==fa) continue;
		dfs2(v,u);
	}
}
int getLCA(int u,int v) {
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=19; i>=0; --i) {
		if(dep[par[u][i]]>=dep[v]) u=par[u][i];
	}
	if(u==v) return u;
	for(int i=19; i>=0; i--) {
		if(par[u][i]!=par[v][i]) {
			u=par[u][i];
			v=par[v][i];
		}
	}
	return par[u][0];
}
void init(int u,int fa) {
	in[u]=++tim;
	for(auto it:adj[u]) {
		int v=it.first,w=it.second;
		if(v==fa) continue;
		init(v,u);
	}
	out[u]=tim;
}
int isp(int u, int v) {
	return in[u] <= in[v] && out[v] <= out[u];
}
bool cmp(int u, int v) {
	return in[u] < in[v];
}
void build(vector<int>&node) {
	sort(node.begin(), node.end(), cmp);
	set<int>node_st;
	for (int x : node)node_st.insert(x);
	for (int i = 1; i < node.size(); i++)node_st.insert(getLCA(node[i - 1], node[i]));
	node.clear();
	for (int x : node_st)node.push_back(x);
	sort(node.begin(), node.end(), cmp);
	vector<int> st;
	for (int v : node) {
		while (!st.empty() && !isp(st.back(), v))
			st.pop_back();
		if (!st.empty())
			vg[st.back()].push_back({ v ,mi[v] });
		st.push_back(v);
	}
}
//node.push_back(1)
//build(node); 

题1 Master of Data Structure

Problem - D - Codeforces

  • 1692167832499.png

    有7种操作,说来惭愧,场上我真准备硬做。。树剖都上了,现在想想真nt

    只有2000次询问,因此点的数量最多有4000个点,即使我对这每个询问跑O(n)都不会超时

    所以对这个询问维护一个虚树,a数组记真实的点的值 w数组记这个a点向上连的边的值 然后像树剖跳链一样往上跳,维护就行了 在67操作的时候 如果向上的边上没有点记得别算上

    vector<array<int,4> > Q;
    vector<int> adj[MAXN],vg[MAXN];
    int par[MAXN][20],dep[MAXN];
    int f[MAXN];
    int w[MAXN];//u点向上的权值
    int a[MAXN];
    int d[MAXN];
    //par[u][i]代表点u的祖先中 深度为max(1,dep[u]-2^i)是谁
    int in[MAXN],out[MAXN],tim=0;
    void dfs(int u,int fa) {
    	dep[u]=dep[fa]+1;
    	par[u][0]=fa;
    	for(int i=1; i<20; ++i) {
    		par[u][i]=par[par[u][i-1]][i-1];
    	}
    	for(auto &v:adj[u]) {
    		if(v==fa) continue;
    		dfs(v,u);
    	}
    }
    int getLCA(int u,int v) {
    	if(dep[u]<dep[v]) swap(u,v);
    	for(int i=19; i>=0; --i) {
    		if(dep[par[u][i]]>=dep[v]) u=par[u][i];
    	}
    	if(u==v) return u;
    	for(int i=19; i>=0; i--) {
    		if(par[u][i]!=par[v][i]) {
    			u=par[u][i];
    			v=par[v][i];
    		}
    	}
    	return par[u][0];
    }
    void init(int u,int fa) {
    	in[u]=++tim;
    	for(auto v:adj[u]) {
    		if(v==fa) continue;
    		init(v,u);
    	}
    	out[u]=tim;
    }
    int isp(int u, int v) {
    	return in[u] <= in[v] && out[v] <= out[u];
    }
    bool cmp(int u, int v) {
    	return in[u] < in[v];
    }
    void build(vector<int>&node) {
    	sort(node.begin(), node.end(), cmp);
    	set<int>node_st;
    	for (int x : node)node_st.insert(x);
    	for (int i = 1; i < node.size(); i++)node_st.insert(getLCA(node[i - 1], node[i]));
    	node.clear();
    	for (int x : node_st)node.push_back(x);
    	sort(node.begin(), node.end(), cmp);
    	vector<int> st;
    	for (int v : node) {
    		while (!st.empty() && !isp(st.back(), v))
    			st.pop_back();
    		if (!st.empty()) {
    			vg[st.back()].push_back(v);
    		}
    		st.push_back(v);
    	}
    }
    void dfs2(int u,int fa) {
    	d[u]=d[fa]+1;
    	f[u]=fa;
    	for(auto &v:vg[u]) {
    		if(v==fa) continue;
    		dfs2(v,u);
    	}
    }
    //node.push_back(1)
    //build(node);
    void work(int u,int v,int k,int op) {
    	int ans=0;
    	if(op==1) {
    		while(u!=v) {
    			assert(u&&v);
    			if(d[u]>d[v]) {
    				a[u]+=k;
    				w[u]+=k;
    				u=f[u];
    			} else {
    				a[v]+=k;
    				w[v]+=k;
    				v=f[v];
    			}
    		}
    		a[u]+=k;
    	} else if(op==2) {
    		while(u!=v) {
    			assert(u&&v);
    			if(d[u]>d[v]) {
    				a[u]^=k;
    				w[u]^=k;
    				u=f[u];
    			} else {
    				a[v]^=k;
    				w[v]^=k;
    				v=f[v];
    			}
    		}
    		a[u]^=k;
    	} else if(op==3) {
    		while(u!=v) {
    			assert(u&&v);
    			if(d[u]>d[v]) {
    				if(a[u]>=k)
    					a[u]-=k;
    				if(w[u]>=k)
    					w[u]-=k;
    				u=f[u];
    			} else {
    				if(a[v]>=k)
    					a[v]-=k;
    				if(w[v]>=k)
    					w[v]-=k;
    				v=f[v];
    			}
    		}
    		if(a[u]>=k) a[u]-=k;
    	} else if(op==4) {
    		while(u!=v) {
    			assert(u&&v);
    			if(d[u]>d[v]) {
    				ans+=(dep[u]-dep[f[u]]-1)*w[u]+a[u];
    				u=f[u];
    			} else {
    				ans+=(dep[v]-dep[f[v]]-1)*w[v]+a[v];
    				v=f[v];
    			}
    		}
    		ans+=a[u];
    		cout<<ans<<"\n";
    	} else if(op==5) {
    		while(u!=v) {
    			assert(u&&v);
    			if(d[u]>d[v]) {
    				ans^=a[u];
    				if((dep[u]-dep[f[u]]-1)%2==1) ans^=w[u];
    				u=f[u];
    			} else {
    				ans^=a[v];
    				if((dep[v]-dep[f[v]]-1)%2==1) ans^=w[v];
    				v=f[v];
    			}
    		}
    		ans^=a[u];
    		cout<<ans<<"\n";
    
    	} else if(op==6) {
    		int mins=a[u],maxs=a[u];
    		while(u!=v) {
    			assert(u&&v);
    			if(d[u]>d[v]) {
    				mins=min(mins,a[u]);
    				if((dep[u]-dep[f[u]]-1))
    					mins=min(mins,w[u]);
    				maxs=max(maxs,a[u]);
    				if((dep[u]-dep[f[u]]-1))
    					maxs=max(maxs,w[u]);
    				u=f[u];
    			} else {
    				mins=min(mins,a[v]);
    				if((dep[v]-dep[f[v]]-1))
    					mins=min(mins,w[v]);
    				maxs=max(maxs,a[v]);
    				if((dep[v]-dep[f[v]]-1))
    					maxs=max(maxs,w[v]);
    				v=f[v];
    			}
    		}
    		mins=min(mins,a[u]);
    		maxs=max(maxs,a[u]);
    		ans=maxs-mins;
    		cout<<ans<<'\n';
    
    	} else if(op==7) {
    		ans=inf;
    		while(u!=v) {
    			assert(u&&v);
    			if(d[u]>d[v]) {
    				ans=min(ans,abs(a[u]-k));
    				if((dep[u]-dep[f[u]]-1))
    					ans=min(ans,abs(w[u]-k));
    				u=f[u];
    			} else {
    				ans=min(ans,abs(a[v]-k));
    				if((dep[v]-dep[f[v]]-1))
    					ans=min(ans,abs(w[v]-k));
    				v=f[v];
    			}
    		}
    		ans=min(ans,abs(a[u]-k));
    		cout<<ans<<"\n";
    	}
    }
    void solve() {
    	int n,q;
    	cin>>n>>q;
    	vector<int> node;
    	Q.clear();
    	tim=0;
    	for(int i=1; i<=n; i++) {
    		for(int j=0; j<20; j++) par[i][j]=0;
    		adj[i].clear();
    		vg[i].clear();
    		a[i]=0;
    		w[i]=0;
    		dep[i]=0;
    		d[i]=0;
    	}
    	for(int i=1; i<n; i++) {
    		int u,v;
    		cin>>u>>v;
    		adj[u].push_back(v);
    		adj[v].push_back(u);
    	}
    	dfs(1,0);
    	init(1,0);
    	set<int> sta;
    	for(int i=1; i<=q; i++) {
    		int op;
    		cin>>op;
    		if(op==1||op==2||op==3||op==7) {
    			int u,v,k;
    			cin>>u>>v>>k;
    			Q.push_back({op,u,v,k});
    			sta.insert(u);
    			sta.insert(v);
    		} else {
    			int u,v;
    			cin>>u>>v;
    			Q.push_back({op,u,v,0});
    			sta.insert(u);
    			sta.insert(v);
    		}
    	}
    	sta.insert(1);
    	assert(sta.size() <= 6000);
    	for(auto &it:sta) node.push_back(it);
    	build(node);
    	dfs2(1,0);
    	for(int i=0; i<q; i++) {
    		int op=Q[i][0],u=Q[i][1],v=Q[i][2],k=Q[i][3];
    		work(u,v,k,op);
    //		cout<<"TEST : \n";
    //		for(int i=1; i<=n; i++) {
    //			cout<<"a: "<<a[i]<<" w: "<<w[i]<<"\n";
    //		}
    	}
    }
    signed main() {
        close;
    	int t;
    	cin>>t;
    	while(t--)
    		solve();
    }
    
    

平衡树

posted @ 2023-08-05 11:45  xishuiw  阅读(30)  评论(0)    收藏  举报