树链剖分习题集

DAY4树链剖分 题解

LCA

这种题肯定是一眼差分题。

那么问题变为:给定\(z\),求\(\sum_{i=1}^rdep[LCA(z,i)]\)

显然可以离线,借助充分利用历史答案的思想,将右端点递增排序。

然后考虑怎么维护。这里需要用到\(dep\)的本质:根到这个点的总共遇到的节点个数。

考虑单求\(dep[LCA(x,y)]\)怎么办。一种方法是求出LCA,另一种方法是暴力跳链法。

暴力的向上跳链不仅仅可以自底向上,也可以自上而下,也即将\((rt,u)\)每个点都加上1,那么\(dep[LCA(u,v)]\)就是\(\sum (rt,v)\)

这启发我们:插入一个点\(x\),就等价于将根到\(x\)路径上的所有点点权+1,故食用一个指针维护操作

一次询问\(z\)的结果就是根到\(z\)的点权和(此时指针等于\(r\))

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
#define N 500050
#define ll long long
const ll p=201314;
struct node{
	int l,r;
	ll sum,lz;
}t[N<<2]; 
struct Node{
	#define lc x<<1
	#define rc x<<1|1
	inline void pushup(int x){
		t[x].sum=t[lc].sum+t[rc].sum;
		t[x].sum%=p; 
	}
	void build(int x,int l,int r){
		t[x].l=l,t[x].r=r,t[x].sum=t[x].lz=0;
		if(l==r)return ;
		int mid=l+r>>1;
		build(lc,l,mid);build(rc,mid+1,r);
	}
	void pushdown(node &a,node &b,node &c){
		b.lz+=a.lz,c.lz+=a.lz;
		b.sum+=(b.r-b.l+1)*a.lz;
		c.sum+=(c.r-c.l+1)*a.lz;
		b.lz%=p,c.lz%=p,b.sum%=p,c.sum%=p;
		a.lz=0;
	}
	void pushdown(int x){
		if(t[x].lz==0)return ;
		pushdown(t[x],t[lc],t[rc]);
	}
	void change(int x,int l,int r,ll k){
		if(l<=t[x].l&&t[x].r<=r){
			t[x].lz+=k,t[x].sum+=k*(t[x].r-t[x].l+1);
			t[x].lz%=p,t[x].sum%=p;
			return ;
		}
		pushdown(x);
		if(l<=t[lc].r)change(lc,l,r,k);
		if(t[lc].r<r)change(rc,l,r,k);
		pushup(x);
	}
	ll find(int x,int l,int r){
		if(l<=t[x].l&&t[x].r<=r)return t[x].sum;
		ll ans=0;
		pushdown(x);
		if(l<=t[lc].r)ans+=find(lc,l,r);
		if(t[lc].r<r)ans+=find(rc,l,r);
		return ans%p;
	}
}s;
struct ask{
	int id,x,u,tag;
	bool operator<(const ask b){
		return x<b.x;
	}
}ask[N];
int ans[N],dfn[N],num,top[N],son[N],siz[N],n,m,tot,head[N],ver[N],nxt[N],f[N],rt,cnt,dep[N];
void add(int u,int v){
	nxt[++tot]=head[u],ver[head[u]=tot]=v;
}
void dfs1(int u,int fa){
	siz[u]=1,dep[u]=dep[f[u]]+1;int len=-1;
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(v==fa)continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(len<siz[v])son[u]=v,len=siz[v];
	}
}
void dfs2(int u,int tp){
	top[u]=tp;dfn[u]=++num;
	if(!son[u])return ;
	dfs2(son[u],tp);
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(v==son[u]||v==f[u])continue;
		dfs2(v,v);
	} 
}
void init(){
	cin>>n>>m;dep[1]=1;
	for(int i=2;i<=n;i++){
		int u;cin>>u;++u;
		f[i]=u;add(u,i);add(i,u);
	}
	dfs1(1,0);dfs2(1,1);
	for(int i=1;i<=m;i++){
		int l,r,u;cin>>l>>r>>u;
		l++,r++,u++;
		ask[++cnt].id=i,ask[cnt].tag=-1,ask[cnt].x=l-1,ask[cnt].u=u;
		ask[++cnt].id=i,ask[cnt].tag= 1,ask[cnt].x=r  ,ask[cnt].u=u;
	}
	sort(ask+1,ask+cnt+1);
	s.build(1,1,n);
}
void updata(int u,int v,int k){
	while(top[u]!=top[v]){
		if(dep[top[u]]>dep[top[v]])swap(u,v);
		s.change(1,dfn[top[v]],dfn[v],k);
		v=f[top[v]];
	}
	if(dep[u]>dep[v])swap(u,v);
	s.change(1,dfn[u],dfn[v],k);
}
ll query(int u,int v){
	ll ans=0;
	while(top[u]!=top[v]){
		if(dep[top[u]]>dep[top[v]])swap(u,v);
		ans+=s.find(1,dfn[top[v]],dfn[v]);
		ans%=p;
		v=f[top[v]];
	}
	if(dep[u]>dep[v])swap(u,v);
	ans+=s.find(1,dfn[u],dfn[v]);
	return ans%p; 
}
void solve(){
	int q=0;
	for(int i=1;i<=cnt;i++){
		while(q<ask[i].x)updata(1,++q,1);
		ans[ask[i].id]+=ask[i].tag*query(1,ask[i].u);
	}
	for(int i=1;i<=m;i++)cout<<(ans[i]%p+p)%p<<endl;
}
int main(){
	ios::sync_with_stdio(false);
	init();solve();
	return 0;
}

骑士的旅行

乱搞。

类比树剖思路,可以对于线段树上每一个节点\([l,r]\)都存上前\(k\)

合并显然:

struct node{
	int res[22];
};
struct tree{
	node res; 
	int l,r;	
}t[N<<2];
struct Node{
	#define lc x<<1
	#define rc x<<1|1
	node pushup(node a,node b){
		int c[45],tot=0;
		node res;memset(res.res,0,sizeof res.res);
		for(int i=1;i<=k;i++){
			c[++tot]=a.res[i],c[++tot]=b.res[i];
		}
		sort(c+1,c+tot+1,cmp);
		for(int i=1;i<=k;i++)res.res[i]=c[i];
		return res;
	}

然后其他是常规操作

对于变迁操作,可以看做一删一加,可以合并起来写

void updata(int x,int pos,int xx,int yy){
		if(t[x].l==t[x].r){
			if(xx!=0)a[rew[pos]].erase(a[rew[pos]].find(xx));
			if(yy!=0)a[rew[pos]].insert(yy);
			memset(t[x].res.res,0,sizeof t[x].res.res);
			int cnt=0;
			for(auto it=a[rew[pos]].begin();it!=a[rew[pos]].end()&&cnt<k;it++)
				t[x].res.res[++cnt]=*it;
			return;
		}
		if(t[lc].r>=pos)updata(lc,pos,xx,yy);
		else updata(rc,pos,xx,yy);
		t[x].res=pushup(t[lc].res,t[rc].res);
	}

剩下的是板子了。

注意数组清零

遥远的国度

线段树维护区间最小值,支持区间赋值

然后对于一个查询分类讨论,设当前根为\(rt\),查询\(x\),以\(1\)为根进行树剖

\(LCA(x,rt)=rt\),直接查\(x\)

\(x\)不属于\(rt\)的子树,直接查\(x\)(没有影响)

\(LCA(x,rt)=x\),这时候分析一下,设\(y\)\(x\)的儿子且是\(rt\)的祖先(或者\(y=rt\)),显然对于dfn[y]~dfn[y]+siz[y]-1都是不能被统计的,求两边取min即可

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define N 500050
struct node{
	int mn,l,r,lz;
}t[N<<2];
int tt;
int head[N],ver[N<<1],nxt[N<<1],tot,num,top[N],dfn[N],rew[N],p,siz[N],son[N],dep[N],f[N][25],n,m,a[N],rt;
struct Node{
	#define lc x<<1
	#define rc x<<1|1
	void pushup(int x){
		t[x].mn=min(t[lc].mn,t[rc].mn);
	}
	void pushdown(node &a,node &b,node &c){
		b.lz=c.lz=a.lz;
		b.mn=c.mn=a.lz;
		a.lz=0;
	}
	void pushdown(int x){
		if(t[x].lz)pushdown(t[x],t[lc],t[rc]);
	}
	void change(int x,int l,int r,int k){
		if(l<=t[x].l&&t[x].r<=r){
			t[x].lz=t[x].mn=k;
			return ;
		}
		pushdown(x);
		if(t[lc].r>=l)change(lc,l,r,k);
		if(t[rc].l<=r)change(rc,l,r,k);
		pushup(x);
	}
	int find(int x,int l,int r){
		if(l<=t[x].l&&t[x].r<=r){
			return t[x].mn;
		}
		pushdown(x);
		int res=0x7fffffff;
		if(t[lc].r>=l)res=min(res,find(lc,l,r));
		if(t[rc].l<=r)res=min(res,find(rc,l,r));
		return res;
	}
	void build(int x,int l,int r){
		t[x].l=l,t[x].r=r,t[x].lz=0;
		if(l==r){
			t[x].mn=a[rew[l]];
			return ;
		}
		int mid=l+r>>1;
		build(lc,l,mid);build(rc,mid+1,r);
		pushup(x); 
	}
}s;
void dfs1(int u,int fa){
	dep[u]=dep[fa]+1,siz[u]=1,f[u][0]=fa;
	for(int i=1;i<=tt;i++)f[u][i]=f[f[u][i-1]][i-1];
	int len=-1;
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(v==fa)continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>len)len=siz[v],son[u]=v;
	}
}
void dfs2(int u,int tp){
	top[u]=tp,dfn[u]=++num,rew[num]=u;
	if(!son[u])return ;
	dfs2(son[u],tp);
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(v==f[u][0]||v==son[u])continue;
		dfs2(v,v);
	}
}
int LCA(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]>dep[top[v]])swap(u,v);
		v=f[top[v]][0];
	}
	if(dep[u]>dep[v])swap(u,v);
	return u;
}
void add(int u,int v){
	nxt[++tot]=head[u],ver[head[u]=tot]=v;
}
void init(){
	cin>>n>>m;tt=log(n)/log(2)+1; 
	for(int i=2;i<=n;i++){
		int u,v;cin>>u>>v;
		add(u,v);add(v,u);
	} 
	for(int i=1;i<=n;i++)cin>>a[i];
	dfs1(1,0);dfs2(1,1);
	s.build(1,1,n);
	cin>>rt;
}
void change(int u,int v,int k){
	int p=0;
	while(top[u]!=top[v]){
		if(dep[top[u]]>dep[top[v]])swap(u,v);
		s.change(1,dfn[top[v]],dfn[v],k);
		v=f[top[v]][0];
	}
	if(dep[u]>dep[v])swap(u,v);
	s.change(1,dfn[u],dfn[v],k);
}
void solve(){
	while(m--){
		int opt,u,v;cin>>opt;
		if(opt==1)cin>>rt;
		if(opt==2){
			int u,v,w;cin>>u>>v>>w;
			change(u,v,w);
		}
		if(opt==3){
			cin>>u;
			int lca=LCA(u,rt);
			if(rt==u){
				cout<<s.find(1,1,n)<<"\n";
			}
			else if(lca==u){
				int v=rt,k=dep[rt]-dep[u]-1;
				for(int i=tt;i>=0;i--){
					if((k>>i)&1)v=f[v][i];
				}
				int ans=s.find(1,1,dfn[v]-1);
				if(dfn[v]+siz[v]<=n){
					ans=min(ans,s.find(1,dfn[v]+siz[v],n));
				}
				cout<<ans<<"\n";
			}
			else {
				cout<<s.find(1,dfn[u],dfn[u]+siz[u]-1)<<"\n";
			}
		}
	}
}
int main(){
	// freopen("P3979_6.in","r",stdin);
	// freopen("P3979_6.ans","w",stdout); 
	ios::sync_with_stdio(false);
	init();solve();
	return 0;
}
/*
3 7
1 2
1 3
1 2 3 
1
3 1
2 1 1 6
3 1
2 2 2 5
3 1 
2 3 3 4
3 1
*/ 

//fc C:\Users\HF01\Desktop\P3979_6.ans C:\Users\HF01\Desktop\P3979_6.out

动态树

由于有去重,维护多个标记又很烦,所以开始乱搞。

考虑——将每一个需要加上的区间存下来(\(O(\log n)\))个

然后将其离散化,运用差分思想将其差分,然后求其前缀和

对于大于1的位置重置为1,再处理出连续的为1区间即可。容易发现这样做区间个数也只有\(O(\log n)\)

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define int long long
#define p (1ll<<31)
#define N 200500
struct node{
	int l,r; ll sum,lz;
}t[N<<2];
int dfn[N],rew[N],head[N],ver[N<<1],nxt[N<<1],f[N],dep[N],son[N],siz[N],top[N],a[N],b[N],c[N],n,m,tot,cnt,num;
int tot2;
struct ask{
	int l,r;
	bool operator<(const ask b){
		return r==b.r?l<b.l:r<b.r;
	}
}ask[N],g[N];
int u1[N],v1[N];
struct Node{
	#define lc x<<1
	#define rc x<<1|1
	void pushup(int x){
		t[x].sum=t[lc].sum+t[rc].sum;
		t[x].sum%=p;
	}
	void pushdown(node &a,node &b,node &c){
		b.lz+=a.lz,c.lz+=a.lz;
		b.sum+=(b.r-b.l+1)*a.lz;
		c.sum+=(c.r-c.l+1)*a.lz;
		a.lz=0;
		b.sum%=p,c.sum%=p;
		b.lz%=p,c.lz%=p;
	}
	void pushdown(int x){
		if(t[x].lz)pushdown(t[x],t[lc],t[rc]);
	}
	void build(int x,int l,int r){
		t[x].l=l,t[x].r=r,t[x].lz=0;
		if(l==r){
			t[x].sum=a[rew[l]];
			return ;
		}
		int mid=l+r>>1;
		build(lc,l,mid);build(rc,mid+1,r);
		pushup(x);
	}
	void updata(int x,int pos,ll k){
		if(t[x].l==t[x].r){
			t[x].sum+=k;
			t[x].sum%=p;
			return ;
		}
		pushdown(x);
		if(t[lc].r>=pos)updata(lc,pos,k);
		else updata(rc,pos,k);
		pushup(x);
	}
	ll find(int x,int l,int r){
		if(l<=t[x].l&&t[x].r<=r)return t[x].sum;
		pushdown(x);
		ll ans=0;
		if(l<=t[lc].r)ans+=find(lc,l,r);
		if(t[rc].l<=r)ans+=find(rc,l,r);
		pushup(x);
		return ans; 
	}
	void change(int x,int l,int r,ll k){
		if(l<=t[x].l&&t[x].r<=r){
			t[x].lz+=k;t[x].sum+=(t[x].r-t[x].l+1)*k;
			t[x].lz%=p;t[x].sum%=p;
			return ;
		}
		pushdown(x);
		if(l<=t[lc].r)change(lc,l,r,k);
		if(t[rc].l<=r)change(rc,l,r,k);
		pushup(x);
	}
}s;
void add(int u,int v){
	nxt[++tot]=head[u],ver[head[u]=tot]=v;
}
void dfs1(int u,int fa){
	dep[u]=dep[fa]+1,siz[u]=1,f[u]=fa;
	int len=-1;
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(v==fa)continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>len)len=siz[v],son[u]=v;
	}
} 
void dfs2(int u,int tp){
	top[u]=tp,dfn[u]=++num,rew[num]=u;
	if(!son[u])return ;
	dfs2(son[u],tp);
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(v==f[u]||v==son[u])continue;
		dfs2(v,v);
	}
}
void find(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]>dep[top[v]])swap(u,v);
		ask[++tot2].l=dfn[top[v]],ask[tot2].r=dfn[v];
		v=f[top[v]];
	}
	if(dep[u]>dep[v])swap(u,v);
	ask[++tot2].l=dfn[u],ask[tot2].r=dfn[v];
}
signed main(){
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=2;i<=n;i++){
		int u,v;cin>>u>>v;
		add(u,v);add(v,u);
	}
	dfs1(1,0);dfs2(1,1);
	s.build(1,1,n);
	cin>>m;
	while(m--){
		int opt;cin>>opt;tot2=0;
		if(opt==0){
			int u,k;cin>>u>>k;
			s.change(1,dfn[u],dfn[u]+siz[u]-1,k);
			continue;
		}
		int k;cin>>k;
		int ans=0;
		for(int i=1;i<=k;i++)cin>>u1[i]>>v1[i];
		for(int i=1;i<=k;i++)find(u1[i],v1[i]);
		int cnt=0;
		for(int i=1;i<=tot2;i++)b[++cnt]=ask[i].l,b[++cnt]=ask[i].r,b[++cnt]=ask[i].r+1;
		sort(b+1,b+cnt+1);
		cnt=unique(b+1,b+cnt+1)-b-1;
		for(int i=0;i<cnt+3;i++)c[i]=0;
		for(int i=1;i<=tot2;i++){
			int u=lower_bound(b+1,b+cnt+1,ask[i].l)-b;
			int v=lower_bound(b+1,b+cnt+1,ask[i].r)-b;
			c[u]++,c[v+1]--;
		}
		for(int i=1;i<=cnt;i++)c[i]+=c[i-1];
		for(int i=1;i<=cnt+1;i++)c[i]=(c[i]>=1);
		int l=1,r=0,tag=0;
		for(int i=1;i<=cnt+1;i++){
			if(c[i]==0&&c[i-1]==1){
				g[++tag].l=b[l],g[tag].r=b[i-1];
			}
			if(c[i]==1&&c[i-1]==0)l=i;
		}
		for(int i=1;i<=tag;i++){
			ans+=s.find(1,g[i].l,g[i].r);
			ans%=p; 
		}
		cout<<ans%p<<"\n";
	}
}

运输计划

显然,虫洞应该建立在最长路径上的某一条边,现将边权下放点权

所以,考虑枚举这条边,问题变为优化查询删掉这条边后的最长路径

\(f_x\)表示删掉\(x\)的最长路径的编号(越长越小),对于一条路径(u,v,w),id=k,考虑它能更新什么

显然,可以将(u,v)上的\(f\)值与k-1min,这样不会影响答案——为什么?

因为,如果这条路径和k-1有重合,重合部分会标记为k-2,即使与k-2有重合也会标为k-3……以此类推,正确性显然。

维护区间最小值,编号从小到大加入,就会使得每个叶子最多更新一次,均摊时间复杂度是\(O(n\log^2 n)\),有一个\(\log n\)是树剖的

然后枚举这条边,单点查即可。

#include<iostream>
#include<algorithm>
#define inf 0x3f3f3f3f
#define ll long long
using namespace std;
#define N 355000
struct Ask{
	int u,v,w,lca;
}ask[N];
bool cmp(Ask a,Ask b){
	return a.w>b.w;
}
int n,m,sum[N],mxw,ans=inf,head[N],LcA,ver[N<<1],nxt[N<<1],tot,cost[N<<1],a[N],dep[N],siz[N],top[N],f[N],son[N],dfn[N],rew[N],num;
void add(int u,int v,int w){
	nxt[++tot]=head[u],ver[head[u]=tot]=v,cost[tot]=w;
}
struct node{
	int tag,mx,lz,l,r;
}t[N<<2];
struct Node{
	#define lc x<<1
	#define rc x<<1|1
	void pushup(int x){
		t[x].tag=t[lc].tag|t[rc].tag;
		if(t[lc].mx!=t[rc].mx)t[x].tag=1;
		t[x].mx=max(t[lc].mx,t[rc].mx);
	}
	void build(int x,int l,int r){
		t[x].l=l,t[x].r=r;
		if(l==r)return ;
		int mid=l+r>>1;
		build(lc,l,mid);build(rc,mid+1,r);
	}
	void pushdown(int x){
		t[lc].lz=t[rc].lz=t[x].lz;
		t[lc].mx=t[rc].mx=t[x].lz;
		t[x].lz=0;
	}
	void update(int x,int l,int r,int k){
		if(l<=t[x].l&&t[x].r<=r&&t[x].tag==0){
			t[x].mx=t[x].lz=k+1;
			return;
		}
		if(t[x].lz)pushdown(x);
		if(t[lc].r>=l&&t[lc].mx>=k)update(lc,l,r,k);
		if(t[rc].l<=r&&t[rc].mx>=k)update(rc,l,r,k);
		pushup(x);
	}
	int find(int x,int pos){
		if(t[x].l==t[x].r)return t[x].mx;
		if(t[x].lz)pushdown(x);
		if(t[lc].r>=pos) return find(lc,pos);
		return find(rc,pos);
	}
}s;
void dfs1(int u,int fa){
	f[u]=fa,dep[u]=dep[fa]+1,siz[u]=1;
	int len=-1;
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(v==fa)continue;
		dfs1(v,u);
		a[v]=cost[i],siz[u]+=siz[v];
		if(len<siz[v])len=siz[v],son[u]=v;
	}
}
void dfs2(int u,int tp){
	top[u]=tp,dfn[u]=++num,rew[num]=u;
	if(!son[u])return ;
	dfs2(son[u],tp);
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(v==f[u]||v==son[u])continue;
		dfs2(v,v);
	}
}
int query(int u,int v){
	int ans=0;
	while(top[u]!=top[v]){
		if(dep[top[u]]>dep[top[v]])swap(u,v);
		ans+=sum[dfn[v]]-sum[dfn[top[v]]-1];
		v=f[top[v]];
	}
	if(dep[u]>dep[v])swap(u,v);
	ans+=sum[dfn[v]]-sum[dfn[u]];
	LcA=u;
	return ans;
}
void change(int u,int v,int k){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		s.update(1,dfn[top[u]],dfn[u],k);
		u=f[top[u]];
	}
	if(dep[u]>dep[v])swap(u,v);
	s.update(1,dfn[u]+1,dfn[v],k);
}
int main(){
//	ios::sync_with_stdio(false);
	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);
		mxw=max(mxw,w);
	}
	dfs1(1,0);
	dfs2(1,1);
	s.build(1,1,n);
	for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[rew[i]];
	for(int i=1;i<=m;i++){
		cin>>ask[i].u>>ask[i].v;
		ask[i].w=query(ask[i].u,ask[i].v);
		ask[i].lca=LcA;
	}
	sort(ask+1,ask+m+1,cmp);
	for(int i=1;i<=m;i++){
		if(ask[i].w<=ask[1].w-mxw) break;
		change(ask[i].u,ask[i].v,i-1);
	}
	int u=ask[1].u,v=ask[1].v,lca=ask[1].lca;
	while(u!=lca)ans=min(ans,max(ask[1].w-a[u],ask[s.find(1,dfn[u])+1].w)),u=f[u];
	while(v!=lca)ans=min(ans,max(ask[1].w-a[v],ask[s.find(1,dfn[v])+1].w)),v=f[v];
	if(ans==inf)ans=0;
	cout<<ans<<"\n";
	return 0;
}

染色

线段树维护颜色块数量就不多说了,树剖向上合并的时候和线段树合并规则是一样的

代码在合并的时候细节有点多,标注在里面了。

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
#define N 500050
struct node{
	int l,r,lx,rx,cnt,lz;
}t[N<<1]; 
node merge(node b,node c){
	if(b.lx==0&&b.rx==0)return c;
	if(c.lx==0&&c.rx==0)return b;
	node ans;
	ans.lx=(b.lx?b.lx:c.lx),ans.rx=(c.rx?c.rx:b.rx);
	ans.cnt=b.cnt+c.cnt-(b.rx==c.lx);
	return ans;
}
int dfn[N],head[N],ver[N<<1],nxt[N<<1],tot,num,n,m;
int son[N],f[N],top[N],dep[N],rew[N],val[N],siz[N];
struct Node{
	#define lc x<<1
	#define rc x<<1|1 
	void pushup(node &a,node &b,node &c){
		a.cnt=b.cnt+c.cnt-(b.rx==c.lx);
		a.lx=b.lx,a.rx=c.rx;
	}
	void pushup(int x){
		pushup(t[x],t[lc],t[rc]);
	}
	void pushdown(node &a,node &b,node &c){
		b.lz=a.lz,c.lz=a.lz;
		b.lx=b.rx=c.lx=c.rx=a.lz;
		b.cnt=1,c.cnt=1;
		a.lz=0;
	}
	void pushdown(int x){
		if(!t[x].lz)return ;
		pushdown(t[x],t[lc],t[rc]);
	}
	void build(int x,int l,int r){
		t[x].l=l,t[x].r=r,t[x].lz=0;
		if(l==r){
			t[x].lx=t[x].rx=val[rew[l]];
			t[x].cnt=1;
			return ; 
		}
		int mid=l+r>>1;
		build(lc,l,mid);build(rc,mid+1,r);
		pushup(x);
	}
	void change(int x,int l,int r,int k){
		if(l<=t[x].l&&t[x].r<=r){
			t[x].cnt=1,t[x].lx=t[x].rx=k;
			t[x].lz=k;
			return ;
		}
		pushdown(x);
		if(l<=t[lc].r)change(lc,l,r,k);
		if(t[rc].l<=r)change(rc,l,r,k);
		pushup(x); 
	} 
	node find(int x,int l,int r){
		if(l<=t[x].l&&t[x].r<=r)return t[x];
		node s1,s2;s1.lx=s1.rx=0;s1.cnt=1;
		s2=s1;
		pushdown(x);
		if(l<=t[lc].r)s1=find(lc,l,r);
		if(t[rc].l<=r)s2=find(rc,l,r);
		pushup(x);return merge(s1,s2);
	}
}s;
void add(int u,int v){
	nxt[++tot]=head[u],ver[head[u]=tot]=v;
}
void dfs1(int u,int fa){
	f[u]=fa,dep[u]=dep[fa]+1,siz[u]=1;
	int len=-1;
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(v==fa)continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>len)son[u]=v,len=siz[v]; 
	}
}
void dfs2(int u,int tp){
	top[u]=tp,dfn[u]=++num,rew[num]=u;
	if(!son[u])return ;
	dfs2(son[u],tp);
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(v==f[u]||v==son[u])continue;
		dfs2(v,v);
	}
}
void init(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>val[i];
	for(int i=2;i<=n;i++){
		int u,v;cin>>u>>v;
		add(u,v);add(v,u); 
	}
	dfs1(1,0);dfs2(1,1);
	s.build(1,1,n);
}
int LCA(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]>dep[top[v]])swap(u,v);
		v=f[top[v]];
	}
	if(dep[u]>dep[v])swap(u,v);
	return u;
}
void change(int u,int v,int k){
	while(top[u]!=top[v]){
		if(dep[top[u]]>dep[top[v]])swap(u,v);
		s.change(1,dfn[top[v]],dfn[v],k);
		v=f[top[v]];
	}
	if(dep[u]>dep[v])swap(u,v);
	s.change(1,dfn[u],dfn[v],k); 
}
int query(int u,int v){
	int k=LCA(u,v);
	node s1,s2;s1.lx=s1.rx=0;s1.cnt=1;
	s2=s1;
	while(top[u]!=top[k]){
		s1=merge(s.find(1,dfn[top[u]],dfn[u]),s1);//由于线段树的关系,只能以下面的链为右儿子
		u=f[top[u]];	
	}
	s1=merge(s.find(1,dfn[k],dfn[u]),s1);
	swap(s1.lx,s1.rx);//但事实上需要u->LCA(u,v)的链是以LCA(u,v)为右端点的(判断合并),故翻转一下区间
	while(top[v]!=top[k]){
		s2=merge(s.find(1,dfn[top[v]],dfn[v]),s2);
		v=f[top[v]];
	}
	s2=merge(s.find(1,dfn[k],dfn[v]),s2);
	return merge(s1,s2).cnt; 
}
void solve(){
	while(m--){
		char x;int u,v,k;
		cin>>x;
		if(x=='Q'){
			cin>>u>>v;
			cout<<query(u,v)<<"\n";
		}
		else {
			cin>>u>>v>>k;
			change(u,v,k);
		}
	}
}
int main(){
	ios::sync_with_stdio(false);
	init();solve();
	return 0;
}

轻重边

NOI真题,典中典。

首先问题在于一类操作,不可能真的修改轻重边,按照树剖的套路,肯定只能修改这条链。那么问题来了,在只能修改链的情况下,如何判断一条边\((u,v)\)是重边。

如果对染色很有灵感的同学,应该可以想到:每一次都给路径上的点染上一种与众不同的颜色,那么\((u,v)\)是重边的充要条件是两点颜色相同。

这就好办了,最初给所有点分别染上颜色\(1\sim n\),然后对于每一次修改染上颜色\(i+n\)即可。

至于线段树维护和树剖的维护,都是比区间合并部分的两端点颜色是否相同即可。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
#define N 550005
struct node{
	int l,r,lx,rx,cnt,lz;
}t[N<<2]; 
inline node merge(node b,node c){
	if(b.lx==0&&b.rx==0)return c;
	if(c.lx==0&&c.rx==0)return b;
	node ans;
	ans.lx=(b.lx?b.lx:c.lx),ans.rx=(c.rx?c.rx:b.rx);
	ans.cnt=b.cnt+c.cnt+(b.rx==c.lx);
	return ans;
}
int dfn[N],head[N],ver[N<<1],nxt[N<<1],tot,num,n,m,color;
int son[N],f[N],top[N],dep[N],rew[N],val[N],siz[N];
struct Node{
	#define lc x<<1
	#define rc x<<1|1 
	inline void pushup(node &a,node &b,node &c){
		a.cnt=b.cnt+c.cnt+(b.rx==c.lx);
		a.lx=b.lx,a.rx=c.rx;
	}
	inline void pushup(int x){
		pushup(t[x],t[lc],t[rc]);
	}
	inline void pushdown(node &a,node &b,node &c){
		b.lz=a.lz,c.lz=a.lz;
		b.lx=b.rx=c.lx=c.rx=a.lz;
		b.cnt=b.r-b.l,c.cnt=c.r-c.l;
		a.lz=0;
	}
	inline void pushdown(int x){
		if(!t[x].lz)return ;
		pushdown(t[x],t[lc],t[rc]);
	}
	void build(int x,int l,int r){
		t[x].l=l,t[x].r=r,t[x].lz=0;
		if(l==r){
			t[x].lx=t[x].rx=val[rew[l]];
			t[x].cnt=0;
			return ; 
		}
		int mid=l+r>>1;
		build(lc,l,mid);build(rc,mid+1,r);
		pushup(x);
	}
	void change(int x,int l,int r,int k){
		if(l<=t[x].l&&t[x].r<=r){
			t[x].cnt=t[x].r-t[x].l,t[x].lx=t[x].rx=k;
			t[x].lz=k;
			return ;
		}
		pushdown(x);
		if(l<=t[lc].r)change(lc,l,r,k);
		if(t[rc].l<=r)change(rc,l,r,k);
		pushup(x); 
	} 
	node find(int x,int l,int r){
		if(l<=t[x].l&&t[x].r<=r)return t[x];
		node s1,s2;s1.lx=s1.rx=0;s1.cnt=1;
		s2=s1;
		pushdown(x);
		if(l<=t[lc].r)s1=find(lc,l,r);
		if(t[rc].l<=r)s2=find(rc,l,r);
		pushup(x);return merge(s1,s2);
	}
}s;
inline void add(int u,int v){
	nxt[++tot]=head[u],ver[head[u]=tot]=v;
}
inline void dfs1(int u,int fa){
	f[u]=fa,dep[u]=dep[fa]+1,siz[u]=1;
	int len=-1;
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(v==fa)continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>len)son[u]=v,len=siz[v]; 
	}
}
inline void dfs2(int u,int tp){
	top[u]=tp,dfn[u]=++num,rew[num]=u;
	if(!son[u])return ;
	dfs2(son[u],tp);
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(v==f[u]||v==son[u])continue;
		dfs2(v,v);
	}
}
inline void init(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)val[i]=i;
	color=n;
	for(int i=2;i<=n;i++){
		int u,v;cin>>u>>v;
		add(u,v);add(v,u); 
	}
	dfs1(1,0);dfs2(1,1);
	s.build(1,1,n);
}
inline int LCA(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]>dep[top[v]])swap(u,v);
		v=f[top[v]];
	}
	if(dep[u]>dep[v])swap(u,v);
	return u;
}
inline void change(int u,int v,int k){
	while(top[u]!=top[v]){
		if(dep[top[u]]>dep[top[v]])swap(u,v);
		s.change(1,dfn[top[v]],dfn[v],k);
		v=f[top[v]];
	}
	if(dep[u]>dep[v])swap(u,v);
	s.change(1,dfn[u],dfn[v],k); 
}
inline int query(int u,int v){
	int k=LCA(u,v);
	node s1,s2;s1.lx=s1.rx=0;s1.cnt=1;
	s2=s1;
	while(top[u]!=top[k]){
		s1=merge(s.find(1,dfn[top[u]],dfn[u]),s1);
		u=f[top[u]];	
	}
	s1=merge(s.find(1,dfn[k],dfn[u]),s1);
	swap(s1.lx,s1.rx); 
	while(top[v]!=top[k]){
		s2=merge(s.find(1,dfn[top[v]],dfn[v]),s2);
		v=f[top[v]];
	}
	s2=merge(s.find(1,dfn[k],dfn[v]),s2);
	return merge(s1,s2).cnt; 
}
inline void solve(){
	while(m--){
		int opt,u,v;
		cin>>opt>>u>>v;
		if(opt==1){
			change(u,v,++color);
		}
		else cout<<query(u,v)-1<<"\n";
	}
}
inline void clear(){
	for(int i=1;i<=n;i++){
		val[i]=dep[i]=son[i]=siz[i]=head[i]=dep[i]=0;
	}
	color=tot=num=0;
}
int main(){
	ios::sync_with_stdio(false);
	int T;cin>>T;
	while(T--){clear();init();solve();}
	return 0;
}

旅游

形式化题意:每次询问支持两个操作

  1. 给定路径\((u,v)\),支持给路径上所有点权值加上\(k\)
  2. 给定路径\((u,v)\),求\(\max_{a\in (u,b),b\in(u,v)}\lbrace val_a-val_b\rbrace\)

考虑简化版问题:给定序列\(a_1\sim a_n\),若干次询问,给定\(l,r\),求\(\max_{l\le p\le q,p\le q\le r}\lbrace a_p-a_r\rbrace\)

这有点像最小子段和的求法——如果我们把\(a\)视作一个前缀和的话。

那么类比最小子段和,不难想到开一棵线段树维护:

  1. \(t[x].mx\)维护区间最大值
  2. \(t[x].mn\)维护区间最小值
  3. \(t[x].ans\)维护答案

考虑维护更新

  1. \(t[x].mx=\max\lbrace t[lc].mx,t[rc].mx\rbrace\)
  2. \(t[x].mn=\min\lbrace t[lc].mn,t[rc].mn\rbrace\)
  3. \(t[x].ans=\max\lbrace t[lc].ans,t[rc].ans,t[rc].mx-t[lc].mn\rbrace\)

这就在\(O((n+m)\log n)\)的时间内解决了这个问题。

我们尝试将其扩展到树上,如果仅仅需要考虑\((u,LCA(u,v))\)这条链的话,这个维护方式已经足够。但我们仍然需要考虑\((LCA(u,v),v)\)这条向下的链,这条链是反着的。

为了维护这条链,在区间上讲就是答案就是从右到左的差值最大值变成了从左到右的差值最大值而已,不妨更改一下维护树剖的线段树定义:

  1. \(t[x].lx\)表示从左到右的答案
  2. \(t[x].rx\)表示从右到左的答案

容易得到:

  1. \(t[x].lx=\max\lbrace t[lc].lx,t[rc].lx,t[lc].mx-t[rc].mn\rbrace\)
  2. \(t[x].rx=\max\lbrace t[rc].rx,t[lc].rx,t[rc].mx-t[lc].mn\rbrace\)

对于树剖各个重链答案合并的问题:将下面的链视作右儿子,上跳到的链视为左儿子,当前答案是父亲来搞就行。

最后思考获取答案的问题:

\(s1,s2\)分别表示\((u,LCA(u,v)),(LCA(u,v),v)\)这两条链最后合并的结果,那么答案显然是:\(\max\lbrace s1.lx,s2.rx,s2.mx-s1.mn\rbrace\)

线段树维护即可。

还有一个区间加的懒标记下传问题:事实上\(t[x].lx,t[x].rx\)不变,\(t[x].mn,t[x].mx\)加上即可。

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
#define N 500500
struct node{
	int l,r,lx,rx,mn,mx,lz;
}t[N<<2];
int n,m,dep[N],head[N],ver[N<<1],nxt[N<<1],val[N],siz[N],dfn[N],num,tot,f[N],son[N],top[N],rew[N];
void pushup(node &a,node &b,node &c){
	a.mn=min(b.mn,c.mn);a.mx=max(b.mx,c.mx);
	a.lx=max(b.lx,c.lx),a.rx=max(b.rx,c.rx);
	a.lx=max(a.lx,b.mx-c.mn);
	a.rx=max(a.rx,c.mx-b.mn);
}
struct Node{
	#define lc x<<1
	#define rc x<<1|1 
	void pushup(node &a,node &b,node &c){
		a.mn=min(b.mn,c.mn);a.mx=max(b.mx,c.mx);
		a.lx=max(b.lx,c.lx),a.rx=max(b.rx,c.rx);
		a.lx=max(a.lx,b.mx-c.mn);
		a.rx=max(a.rx,c.mx-b.mn);
	}
	void pushdown(node &a,node &b,node &c){
		b.lz+=a.lz,c.lz+=a.lz;
		b.mn+=a.lz,b.mx+=a.lz;
		c.mn+=a.lz,c.mx+=a.lz;
		a.lz=0;
	}
	void pushup(int x){
		pushup(t[x],t[lc],t[rc]);
	}
	void pushdown(int x){
		if(!t[x].lz)return ;
		pushdown(t[x],t[lc],t[rc]);
	}
	void build(int x,int l,int r){
		t[x].l=l,t[x].r=r,t[x].lz=0;
		if(l==r){
			t[x].mn=t[x].mx=val[rew[l]];
			t[x].lx=t[x].rx=0;
			return ;
		}
		int mid=l+r>>1;
		build(lc,l,mid),build(rc,mid+1,r);
		pushup(x);
	}
	void change(int x,int l,int r,int k){
		if(l<=t[x].l&&t[x].r<=r){
			t[x].mn+=k,t[x].mx+=k;
			t[x].lz+=k;
			return ;
		}
		pushdown(x);
		if(l<=t[lc].r)change(lc,l,r,k);
		if(t[rc].l<=r)change(rc,l,r,k);
		pushup(x);
	}
	node find(int x,int l,int r){
		if(l<=t[x].l&&t[x].r<=r)return t[x];
		pushdown(x);
		node s1,s2,ans;s1.lx=s1.rx=0,
		s1.mn=1e9,s1.mx=-1e9,s2=s1;
		if(l<=t[lc].r)s1=find(lc,l,r);
		if(t[rc].l<=r)s2=find(rc,l,r);
		pushup(ans,s1,s2);
		return ans;
	}
}s;
void dfs1(int u,int fa){
	f[u]=fa,dep[u]=dep[fa]+1,siz[u]=1;
	int len=-1;
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(v==fa)continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>len)son[u]=v,len=siz[v];
	} 
}
void dfs2(int u,int tp){
	top[u]=tp,dfn[u]=++num,rew[num]=u;
	if(!son[u])return ;
	dfs2(son[u],tp);
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(v==son[u]||v==f[u])continue;
		dfs2(v,v);
	}
}
void change(int u,int v,int k){
	while(top[u]!=top[v]){
		if(dep[top[u]]>dep[top[v]])swap(u,v);
		s.change(1,dfn[top[v]],dfn[v],k);
		v=f[top[v]];
	}
	if(dep[u]>dep[v])swap(u,v);
	s.change(1,dfn[u],dfn[v],k);
}
int LCA(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]>dep[top[v]])swap(u,v);
		v=f[top[v]];
	}
	if(dep[u]>dep[v])swap(u,v);
	return u;
}
int query(int u,int v){
	int k=LCA(u,v);
	node s1,s2,s3,s4;s1.lx=s1.rx=0,
	s1.mn=1e9,s1.mx=-1e9,s2=s3=s4=s1;
	while(top[u]!=top[k]){
		s3=s.find(1,dfn[top[u]],dfn[u]),s4=s1;
		pushup(s1,s3,s4);
		u=f[top[u]];
	}
	s3=s.find(1,dfn[k],dfn[u]),s4=s1;
	pushup(s1,s3,s4);
	while(top[v]!=top[k]){
		s3=s.find(1,dfn[top[v]],dfn[v]),s4=s2;
		pushup(s2,s3,s4);
		v=f[top[v]];
	}
	s3=s.find(1,dfn[k],dfn[v]),s4=s2;
	pushup(s2,s3,s4);
	return max(max(s1.lx,s2.rx),s2.mx-s1.mn);
}
void add(int u,int v){
	nxt[++tot]=head[u],ver[head[u]=tot]=v;
}
void init(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>val[i];
	for(int i=1;i<n;i++){
		int u,v;cin>>u>>v;
		add(u,v);add(v,u);
	}
	cin>>m;
	dfs1(1,0);dfs2(1,1);
	s.build(1,1,n);
}
void solve(){
	while(m--){
		int u,v,w;
		cin>>u>>v>>w;
		cout<<query(u,v)<<"\n";
		change(u,v,w);
	}
}
int main(){
	ios::sync_with_stdio(false);
	init();solve();
	return 0;
}

posted @ 2023-02-02 10:50  spdarkle  阅读(12)  评论(0编辑  收藏  举报