【点分治+二分+单调队列】wc2010重建计划

传送门

 

参考博客

所有边减去mid,看树上有没有边权和大于等于0的路径。可以用点分治。

枚举当前分治子树内的结点u,那么我们就要找到之前深度范围为[L-dep[u],R-dep[u]]的最大值。(到当前重心的边权和)

如果用线段树维护,查询和更新都要套一个log,时间要炸。

我们发现如果dep是单增的,那么就相当于是一个滑动窗口问题。可以用一个单调队列维护。

我们用深度记录这个单调队列。我们只要保证深度单调,距离单减就好,因为要维护距离的最大值。用深度判断是否符合[L,U]的长度要求。其实就是判断队列内元素是否在窗口内。

如果我们枚举的深度单增,那么另一边的深度单减,先保证下限L,再保证上限U。反之先保证R,再保证L。

然而每次滑动窗口的复杂度是由  max(之前的最大深度,当前子树大小)  决定的。点分治只能保证子树的大小,不能保证深度。于是网上大部分题解可以用一个扫把图卡成n²log,233。

 

然而我们只要先把子树按照最大深度排个序就好了。

具体实现的时候,就是先从当前重心dfs,得到每个子树的最大深度以及每种深度的最大权值。

用f[i]表示之前已经搜过的子树中,深度为i的最大边权和。

用g[i]表示现在正在枚举的子树中,深度为i的最大边权和。

用now_mxdp[u]表示u子树中最大深度,用predep表示之前已经搜索过的子树中可以达到的最大深度。

然后二分mid,在f和g数组中暴力把(mid*深度)减去。

滑动窗口做完之后记得把f数组和g数组给加回去。然后还要更新f数组。

每搜完一个子树需要把g数组给重置。每搜完一个重心需要把f数组给重置。

 

大概就是这样。最后,注意精度。

二分写点分里面比较好,因为可以不停更新二分左边界。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
const double inf=1e9;
const double eps=1e-9;
int Head[maxn],Next[maxn<<1],V[maxn<<1],W[maxn<<1],cnt=0;
int dep[maxn],siz[maxn],maxsub[maxn],vis[maxn],tot=0;
int now_mxdp[maxn],to[maxn];
int n,L,U,root=0,SZ,a,b,c;
int nowdep,predep,head,tail;
double dis[maxn],up=-inf,f[maxn],g[maxn],ans=inf;
inline int read(){
	int x=0;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return x;
}
inline void add(int u,int v,int w){
	++cnt;
	Next[cnt]=Head[u];
	V[cnt]=v,W[cnt]=w;
	Head[u]=cnt;
}
inline void getroot(int u,int f){
	maxsub[u]=0,siz[u]=1;
	for(int i=Head[u];i;i=Next[i]) if(V[i]!=f && !vis[V[i]]){
		getroot(V[i],u);
		maxsub[u]=max(maxsub[u],siz[V[i]]);
		siz[u]+=siz[V[i]];
	}
	maxsub[u]=max(maxsub[u],SZ-siz[u]);
	if(maxsub[root]>maxsub[u]) root=u;
}
void get_dis(int u,int f,int dep,double val){
	nowdep=max(nowdep,dep),dis[u]=val;
	for(int i=Head[u];i;i=Next[i]) if(V[i]!=f && !vis[V[i]])
		get_dis(V[i],u,dep+1,(double)W[i]+val);
}
void get_g(int u,int f,int dep){
	g[dep]=max(g[dep],dis[u]); 
	for(int i=Head[u];i;i=Next[i]) if(!vis[V[i]] && V[i]!=f)
		get_g(V[i],u,dep+1);
}
bool check(double M,bool ret=0){
	for(int i=1;i<=nowdep;++i) g[i]-=(double)i*M;
	for(int i=1;i<=predep;++i) f[i]-=(double)i*M;
	deque<int> Q;int now=predep;
	for(int i=0;i<=nowdep&&(!ret);++i){
		while(((now+i)>=L)&&(now>=0)){
			while((!Q.empty())&&(f[Q.back()]<f[now])) Q.pop_back();
			Q.push_back(now--);
		}
		while((!Q.empty())&&((Q.front()+i)>U)) Q.pop_front();
		if((!Q.empty())&&(f[Q.front()]+g[i]>=0)) ret=1;
	}
	for(int i=1;i<=nowdep;++i) g[i]+=(double)i*M;
	for(int i=1;i<=predep;++i) f[i]+=(double)i*M;
	return ret;
}
bool cmp(const int &a,const int &b){return now_mxdp[a]<now_mxdp[b];}
void solve(int u){
	vis[u]=1,predep=0,dis[u]=0.0,tot=0;
	for(int i=Head[u];i;i=Next[i]) if(!vis[V[i]]){
		to[++tot]=V[i],nowdep=0;
		get_dis(V[i],u,1,(double)W[i]);
		now_mxdp[V[i]]=nowdep;
	}
	sort(to+1,to+tot+1,cmp);
	for(int i=1;i<=tot;++i){
		nowdep=now_mxdp[to[i]];
		get_g(to[i],u,1);
		double l=ans,r=up;
		while(r-l>eps){
			double mid=(l+r)/2.0;
			if(check(mid)) l=mid;
			else r=mid;
		}
		ans=r,predep=max(predep,nowdep);
		for(int i=1;i<=nowdep;++i) f[i]=max(f[i],g[i]),g[i]=-inf;
	}
	for(int i=1;i<=predep;++i) f[i]=-inf;
	for(int i=Head[u];i;i=Next[i]) if(!vis[V[i]])
	root=0,SZ=siz[V[i]],getroot(V[i],u),solve(root);
}
int main(){
	maxsub[0]=SZ=n=read(),L=read(),U=read();
	for(int i=1;i<n;++i){
		a=read(),b=read(),c=read();
		ans=min(ans,double(c));
		up=max(up,double(c));
		add(a,b,c),add(b,a,c);
	}
	for(int i=1;i<=n;++i) g[i]=f[i]=-inf;
	getroot(1,0),solve(root);
	printf("%.3lf\n",ans);
}

如果二分写在点分治外面,要记一下重心,免得每次都要求重心。代码如下:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
const double inf=1e9;
const double eps=1e-9;
int Head[maxn],Next[maxn<<1],V[maxn<<1],W[maxn<<1],cnt=0;
int dep[maxn],siz[maxn],maxsub[maxn],vis[maxn],tot=0;
int now_mxdp[maxn],to[maxn],rt[maxn<<1];
int n,L,U,root=0,RT=0,SZ,a,b,c;
int nowdep,predep,head,tail;
double dis[maxn],down=inf,up=-inf,f[maxn],g[maxn],ans,l,r;
inline int read(){
	int x=0;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return x;
}
inline void add(int u,int v,int w){
	++cnt;
	Next[cnt]=Head[u];
	V[cnt]=v,W[cnt]=w;
	Head[u]=cnt;
}
inline void getroot(int u,int f){
	maxsub[u]=0,siz[u]=1;
	for(int i=Head[u];i;i=Next[i]) if(V[i]!=f && !vis[V[i]]){
		getroot(V[i],u);
		maxsub[u]=max(maxsub[u],siz[V[i]]);
		siz[u]+=siz[V[i]];
	}
	maxsub[u]=max(maxsub[u],SZ-siz[u]);
	if(maxsub[root]>maxsub[u]) root=u;
}
void get_dis(int u,int f,int dep,double val,double M){
	nowdep=max(nowdep,dep),dis[u]=val;
	for(int i=Head[u];i;i=Next[i]) if(V[i]!=f && !vis[V[i]])
		get_dis(V[i],u,dep+1,(double)W[i]-M+val,M);
}
void get_g(int u,int f,int dep){
	g[dep]=max(g[dep],dis[u]); 
	for(int i=Head[u];i;i=Next[i]) if(!vis[V[i]] && V[i]!=f)
		get_g(V[i],u,dep+1);
}
bool check(){
	deque<int> Q;int now=predep;
	for(int i=0;i<=nowdep;++i){
		while(((now+i)>=L)&&(now>=0)){
			while((!Q.empty())&&(f[Q.back()]<f[now])) Q.pop_back();
			Q.push_back(now--);
		}
		while((!Q.empty())&&((Q.front()+i)>U)) Q.pop_front();
		if((!Q.empty())&&(f[Q.front()]+g[i]>=0)) return true;
	}
	return false;
}
bool cmp(const int &a,const int &b){return now_mxdp[a]<now_mxdp[b];}
bool solve(int u,double M,bool flag=0){
	vis[u]=1,predep=dis[u]=tot=0;
	for(int i=Head[u];i;i=Next[i]) if(!vis[V[i]]){
		to[++tot]=V[i],nowdep=0;
		get_dis(V[i],u,1,(double)W[i]-M,M);
		now_mxdp[V[i]]=nowdep;
	}
	sort(to+1,to+tot+1,cmp);
	for(int i=1;((i<=tot)&&(!flag));++i){
		nowdep=now_mxdp[to[i]],get_g(to[i],u,1);
		if(check()) flag=1;
		predep=max(predep,nowdep);
		for(int i=1;i<=nowdep;++i) f[i]=max(f[i],g[i]),g[i]=-inf;
	}
	for(int i=1;i<=predep;++i) f[i]=-inf;
	if(flag) return true;
	for(int i=Head[u];i;i=Next[i]) if(!vis[V[i]]){
		if(rt[i]){
			if(solve(rt[i],M))
			return true;
		}
		else{
			root=0,SZ=siz[V[i]],getroot(V[i],u),rt[i]=root;
			if(solve(rt[i],M))
			return true;
		}
	}
	return false;
}
int main(){
	freopen("test.in","r",stdin);
	freopen("ans.out","w",stdout);
	maxsub[0]=SZ=n=read(),L=read(),U=read();
	for(int i=1;i<n;++i){
		a=read(),b=read(),c=read();
		ans=min(ans,double(c));
		up=max(up,double(c));
		add(a,b,c),add(b,a,c);
	}
	for(int i=1;i<=n;++i) f[i]=g[i]=-inf;
	r=up,l=ans;
	getroot(1,0),RT=root;
	while(r-l>eps){
		double mid=(l+r)/2.0;
		for(int i=1;i<=n;++i) vis[i]=0;
		if(solve(RT,mid)) ans=mid,l=mid;
		else r=mid;
	}
	printf("%.3lf\n",ans);
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值