ybtAu「树上问题」第2章 点分治

A. 【例题1】树上问题

板子,树状数组统计即可。

#include <iostream>
#define N 80005
#define int long long
int n,K,hed[N],tal[N],wt[N],nxt[N],cnte,ans;
void de(int u,int v,int w) {tal[++cnte]=v,wt[cnte]=w,nxt[cnte]=hed[u],hed[u]=cnte;}
namespace BIT
{
	int f[N],r;
	void c(int x,int t) {for(;x<=K;x+=x&-x) f[x]+=t;}
	int q(int x) {for(r=0;x>0;x-=x&-x) r+=f[x];return r;}
};
namespace DT
{
	int mx,trt,siz[N],son[N],vis[N],st[N],tp;
	void grt(int x,int fa,int tot)
	{
		siz[x]=1;
		for(int i=hed[x];i;i=nxt[i]) if(tal[i]!=fa&&!vis[tal[i]])
			grt(tal[i],x,tot),siz[x]+=siz[tal[i]],son[x]=std::max(son[x],siz[tal[i]]);
		son[x]=std::max(son[x],tot-siz[x]);
		if(son[x]<mx) mx=son[x],trt=x;
	}
	void gdis(int x,int fa,int dis)
	{
		st[++tp]=dis;
		for(int i=hed[x];i;i=nxt[i]) if(tal[i]!=fa&&!vis[tal[i]]) gdis(tal[i],x,dis+wt[i]);
	}
	void solve(int x,int tot)
	{
		mx=1e9,grt(x,x,tot),x=trt;
		vis[x]=1;
		for(int i=hed[x];i;i=nxt[i]) if(!vis[tal[i]])
		{
			int lt=tp;
			gdis(tal[i],x,wt[i]);
			for(int p=tp;p>lt;p--) ans+=BIT::q(K-st[p]);
			for(int p=tp;p>lt;p--) if(st[p]<=K) BIT::c(st[p],1),ans++;
		}
		for(;tp;tp--) BIT::c(st[tp],-1);
		for(int i=hed[x];i;i=nxt[i]) if(!vis[tal[i]]) solve(tal[i],tot-son[tal[i]]);
	}
};
signed main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	std::cin>>n;
	for(int i=1,u,v,w;i<n;i++) std::cin>>u>>v>>w,de(u,v,w),de(v,u,w);
	std::cin>>K;
	DT::solve(1,n);
	std::cout<<ans;
}

B. 【例题2】聪聪可可

板子,开两个大小为 \(3\) 的桶即可。注意要加上到分治中心的链和长度为 \(0\) 的链。

#include <iostream>
#define N 80005
#define int long long
int n,hed[N],tal[N],wt[N],nxt[N],cnte,ans;
void de(int u,int v,int w) {tal[++cnte]=v,wt[cnte]=w,nxt[cnte]=hed[u],hed[u]=cnte;}
int buc[3];
namespace DT
{
	int mx,trt,siz[N],son[N],vis[N],st[3],tp;
	void grt(int x,int fa,int tot)
	{
		siz[x]=1;
		for(int i=hed[x];i;i=nxt[i]) if(tal[i]!=fa&&!vis[tal[i]])
			grt(tal[i],x,tot),siz[x]+=siz[tal[i]],son[x]=std::max(son[x],siz[tal[i]]);
		son[x]=std::max(son[x],tot-siz[x]);
		if(son[x]<mx) mx=son[x],trt=x;
	}
	void gdis(int x,int fa,int dis)
	{
		st[dis%3]++;
		for(int i=hed[x];i;i=nxt[i]) if(tal[i]!=fa&&!vis[tal[i]]) gdis(tal[i],x,dis+wt[i]);
	}
	void solve(int x,int tot)
	{
		mx=1e9,grt(x,x,tot),x=trt;
		vis[x]=1;
		for(int i=hed[x];i;i=nxt[i]) if(!vis[tal[i]])
		{
			gdis(tal[i],x,wt[i]);
			ans+=2*st[0];
			for(int j=0;j<3;j++) ans+=2*buc[(3-j)%3]*st[j];
			for(int j=0;j<3;j++) buc[j]+=st[j],st[j]=0;
		}
		ans++;
		buc[0]=buc[1]=buc[2]=0;
		for(int i=hed[x];i;i=nxt[i]) if(!vis[tal[i]]) solve(tal[i],tot-son[tal[i]]);
	}
};
int gcd(int x,int y) {return y?gcd(y,x%y):x;}
signed main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	std::cin>>n;
	for(int i=1,u,v,w;i<n;i++) std::cin>>u>>v>>w,de(u,v,w),de(v,u,w);
	DT::solve(1,n);
	int sum=n*n;
	int tgcd=gcd(ans,sum);
	std::cout<<ans/tgcd<<'/'<<sum/tgcd;
}

C. 重建计划

01 分数规划。
二分答案,把每条边的边权减去 \(mid\),转化为判断是否存在长度 \(\in[L,U]\) 且权值和为正的路径。
点分治,用桶 \(buc_i\) 记录已经处理的子树中,从分治中心出发,长度为 \(i\) 的路径的最大权值;处理一棵子树时,用桶 \(cur_i\) 记录子树内的权值。
令路径的一个端点在已经处理的子树中,另一个端点在当前子树内,那么对于子树内深度为 \(i\) 的节点,可以用 \(cur_i+buc_j(j\in[L-i,R-i])\) 来更新答案。
如果从小到大枚举 \(i\) 的话,发现区间左右端点是递减的,可以使用单调队列求解。
注意实现细节,数组要清干净。

#include <iostream>
#include <vector>
#define N 200005
int n,L,U,hed[N],deh[N],tal[N<<1],wt[N],nxt[N<<1],cnte,rt;
void de(int u,int v,int w) {tal[++cnte]=v,wt[cnte]=w,nxt[cnte]=hed[u],hed[u]=cnte;}
void ed(int u,int v) {tal[++cnte]=v,nxt[cnte]=deh[u],deh[u]=cnte;}
namespace DT
{
	int mn,trt,siz[N],son[N],vis[N],st[N],tp,mdep,q[N],tot;
	double mid,buc[N],cur[N];
	void gtot(int x,int fa)
	{
		tot++;
		for(int i=hed[x];i;i=nxt[i]) if(tal[i]!=fa&&!vis[tal[i]]) gtot(tal[i],x);
	}
	void grt(int x,int fa)
	{
		siz[x]=son[x]=1;
		for(int i=hed[x];i;i=nxt[i]) if(tal[i]!=fa&&!vis[tal[i]])
			grt(tal[i],x),siz[x]+=siz[tal[i]],son[x]=std::max(son[x],siz[tal[i]]);
		son[x]=std::max(son[x],tot-siz[x]);
		if(son[x]<mn) mn=son[x],trt=x;
	}
	int build(int x)
	{
		tot=0,gtot(x,x),mn=1e9,grt(x,x),x=trt;
		vis[x]=1;
		for(int i=hed[x];i;i=nxt[i]) if(!vis[tal[i]])
			ed(x,build(tal[i]));
		vis[x]=0;
		return x;
	}
	void gdis(int x,int fa,int dep,double dis)
	{
		mdep=std::max(dep,mdep);
		cur[dep]=std::max(cur[dep],dis);
		for(int i=hed[x];i;i=nxt[i]) if(tal[i]!=fa&&!vis[tal[i]])
			gdis(tal[i],x,dep+1,dis+wt[i]-mid);
	}
	bool check(int x)
	{
		vis[x]=1;
		int mt=0,fg=0;
		for(int i=hed[x];i;i=nxt[i]) if(!vis[tal[i]])
		{
			mdep=0,gdis(tal[i],x,1,wt[i]-mid);
			int hd=1,tl=0,p=mt;
			for(int j=1;j<=mdep;j++)
			{
				while(hd<=tl&&q[hd]+j>U) hd++;
				while(p>=L-j&&p>=0)
				{
					while(hd<=tl&&buc[q[tl]]<=buc[p]) tl--;
					q[++tl]=p,p--;
				}
				if(hd<=tl&&buc[q[hd]]+cur[j]>0) {fg=1;break;}
			}
			for(int j=1;j<=mdep;j++) buc[j]=std::max(buc[j],cur[j]),cur[j]=-1e9;
			mt=std::max(mt,mdep);
			if(fg) break;
		}
		for(int i=1;i<=mt;i++) buc[i]=-1e9;
		if(fg) return vis[x]=0,1;
		for(int i=deh[x];i;i=nxt[i]) if(check(tal[i])) return vis[x]=0,1;
		vis[x]=0;
		return fg;
	}
};
bool check(double mid)
{
	//printf("check %.2lf\n",mid);
	DT::mid=mid;
	return DT::check(rt);
}
signed main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	std::cin>>n>>L>>U;
	double l=1e9,r=0;
	for(int i=1;i<=n;i++) DT::buc[i]=DT::cur[i]=-1e9;
	for(int i=1,u,v,w;i<n;i++) std::cin>>u>>v>>w,de(u,v,w),de(v,u,w),l=std::min(l,1.0*w),r=std::max(r,1.0*w);
	rt=DT::build(1);
	while(r-l>1e-4)
	{
		double mid=(l+r)/2;
		if(check(mid)) l=mid;
		else r=mid;
	}
	printf("%.3lf",l);
}

D. 树上GCD

E. 幸运数字

树剖 + 线段树维护区间线性基。

#include <iostream>
#include <cstring>
#include <cassert>
#define int long long
#define N 40005
int n,q,hed[N],tal[N],nxt[N],cnte,a[N];
void de(int u,int v) {tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte;}
struct Base
{
	int d[61];
	Base() {memset(d,0,sizeof d);}
	void ins(int x)
	{
		for(int i=60;i>=0;i--) if(x>>i&1)
		{
			if(d[i]) x^=d[i];
			else return d[i]=x,void();
		}
	}
	int qr()
	{
		int ret=0;
		for(int i=60;i>=0;i--) if(d[i]&&!(ret>>i&1)) ret^=d[i];
		return ret;
	}
};
Base mg(Base x,Base y)
{
	for(int i=60;i>=0;i--) if(y.d[i]) x.ins(y.d[i]);
	return x;
}
namespace SGT
{
	Base d[N<<2];
	#define mid (lb+rb>>1)
	void md(int x,int t,int k,int lb,int rb)
	{
		d[x].ins(k);
		if(lb<rb) (t<=mid)?md(x<<1,t,k,lb,mid):md(x<<1|1,t,k,mid+1,rb);
	}
	Base qr(int x,int l,int r,int lb,int rb)
	{
		if(l<=lb&&rb<=r) return d[x];
		if(r<=mid) return qr(x<<1,l,r,lb,mid);
		if(l>mid) return qr(x<<1|1,l,r,mid+1,rb);
		return mg(qr(x<<1,l,r,lb,mid),qr(x<<1|1,l,r,mid+1,rb));
	}
	#undef mid
};
namespace HLD
{
	int dfn[N],dep[N],fa[N],son[N],siz[N],top[N],idx;
	void d1(int x)
	{
		siz[x]=1;
		for(int i=hed[x];i;i=nxt[i]) if(!siz[tal[i]])
		{
			fa[tal[i]]=x,dep[tal[i]]=dep[x]+1,d1(tal[i]),siz[x]+=siz[tal[i]];
			if(siz[tal[i]]>siz[son[x]]) son[x]=tal[i];
		}
	}
	void d2(int x,int tp)
	{
		if(!x) return;
		dfn[x]=++idx,d2(son[x],top[x]=tp);
		for(int i=hed[x];i;i=nxt[i]) if(!top[tal[i]]) d2(tal[i],tal[i]);
	}
	int qr(int x,int y)
	{
		Base ans;
		while(top[x]!=top[y])
		{
			if(dep[top[x]]<dep[top[y]]) std::swap(x,y);
			ans=mg(ans,SGT::qr(1,dfn[top[x]],dfn[x],1,n)),x=fa[top[x]];
		}
		if(dep[x]>dep[y]) std::swap(x,y);
		return mg(ans,SGT::qr(1,dfn[x],dfn[y],1,n)).qr();
	}
};
signed main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	std::cin>>n>>q;
	for(int i=1;i<=n;i++) std::cin>>a[i];
	for(int i=1,u,v;i<n;i++) std::cin>>u>>v,de(u,v),de(v,u);
	HLD::d1(1),HLD::d2(1,1);
	for(int i=1;i<=n;i++) SGT::md(1,HLD::dfn[i],a[i],1,n);
	for(int i=1,u,v;i<=q;i++) std::cin>>u>>v,std::cout<<HLD::qr(u,v)<<'\n';
}

F. 购票问题

posted @ 2025-07-07 07:54  整齐的艾萨克  阅读(10)  评论(0)    收藏  举报