【bzoj1758】[Wc2010]重建计划 二分答案+单调队列+点分治

27 篇文章 0 订阅
15 篇文章 0 订阅

首先二分答案ans,每条边权值减去ans,问题转化成整棵树中长度在[L,U]之间,权值和最大的路径是否大于0.

点分治

考虑如何求出经过根的所有路径对答案的影响,

枚举根的每个儿子,

g[i]表示当前子树中深度为i的节点的最大权值和

f[i]表示之前的子树中深度为i的节点的最大权值和

枚举i,查询f[L-i]~f[U-i]之间的最大值,这个可以用线段树来做,复杂度是O(∑size log n)的

但是,题解上说用单调队列来做,因为随着i的增大,询问区间是单调向左平移的。

但是这样的复杂度好像是不对的

比如,一棵树,根节点有10^5个深度为1的儿子,还有1个深度为10^5的儿子是一条链,这样的话每次枚举都会把深度为10^5的那条链重新计算一遍,所以复杂度会变成O(n^2)

写这道题的时候感觉不太对,晨神hack了一下,果然有问题,不过既然已经写了单调队列,那就将错就错吧。

写的时候,每次要做完整个儿子,再更新f数组,因为这个错误调了好久。


#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<iostream>
#define maxn 100010
#define inf 1e15
#define eps 1e-5

using namespace std;

int head[maxn],to[maxn*2],next[2*maxn],fa[maxn],size[maxn];
double dep[maxn],f[maxn],g[maxn],len[2*maxn];
int q[maxn];
bool vis[maxn];
int n,m,num,L,U,l,r;
double Ans,ans,mid,sum;

void addedge(int x,int y,double z)
{
	num++;to[num]=y;len[num]=z;next[num]=head[x];head[x]=num;
}

void get_root(int x,int Size,int &cg)
{
	bool flag=1;
	size[x]=1;
	for (int p=head[x];p;p=next[p])
	  if (!vis[to[p]] && to[p]!=fa[x])
	  {
	  	fa[to[p]]=x;
	  	get_root(to[p],Size,cg);
	  	size[x]+=size[to[p]];
	  	if (size[to[p]]>Size/2) flag=0; 
	  }
	if (Size-size[x]>Size/2) flag=0;
	if (flag) cg=x;
}

void dfs1(int x)
{
	for (int p=head[x];p;p=next[p])
	  if (!vis[to[p]] && to[p]!=fa[x]) fa[to[p]]=x,dfs1(to[p]); 
} 

void dfs2(int x,int d,int &mx)
{
	mx=max(mx,d);
	g[d]=max(g[d],dep[x]);
	for (int p=head[x];p;p=next[p])
	  if (to[p]!=fa[x] && !vis[to[p]])
	  {
	  	dep[to[p]]=dep[x]+len[p]-mid;
	  	dfs2(to[p],d+1,mx);
	  }
}

void calc(int x)
{
	f[0]=0.0;dep[x]=0.0;
	int mx=0;
	for (int p=head[x];p;p=next[p])
	  if (to[p]!=fa[x] && !vis[to[p]])
	  {
	  	int dp=0;
	  	dep[to[p]]=dep[x]+len[p]-mid;
	  	dfs2(to[p],1,dp);
		mx=max(mx,dp);
	  	int j=mx;
	  	l=1,r=0;
	  	for (int i=1;i<=dp;i++)
	  	{
	  		while (j>=0 && i+j>=L)
	  		{
	  			while (l<=r && f[q[r]]<f[j]) q[r--]=0; 
	  			q[++r]=j--;
	  		}
	  		while (l<=r && q[l]+i>U) q[l++]=0;
	  		if (l<=r && L<=i+q[l] && i+q[l]<=U) ans=max(ans,g[i]+f[q[l]]);
	  	}
	  	for (int i=1;i<=dp;i++) f[i]=max(f[i],g[i]),g[i]=-inf;
	  }
	for (int i=1;i<=mx;i++) f[i]=-inf;
}

void solve(int x,int Size)
{
	if (Size<=L) return;
	int cg;
	fa[x]=0;
	get_root(x,Size,cg);
	size[fa[cg]]=Size-size[cg];
	fa[cg]=0;vis[cg]=1;
	dfs1(cg);
	double l=0.0,r=1000000.0;
	while (r-l>eps)
	{
		mid=(l+r)/2.0;
		ans=-1;calc(cg);
		if (ans>=0) l=mid; else r=mid;
	}
	Ans=max(Ans,l);
	for (int p=head[cg];p;p=next[p]) if (!vis[to[p]]) solve(to[p],size[to[p]]);
}

int main()
{
	scanf("%d",&n);
	scanf("%d%d",&L,&U);
	for (int i=1;i<n;i++)
	{
		int x,y;
		double z;
		scanf("%d%d%lf",&x,&y,&z);
		addedge(x,y,z);addedge(y,x,z);	
	}
	Ans=0.0;
	for (int i=1;i<=n;i++) f[i]=g[i]=-inf;
	solve(1,n);
	printf("%.3lf\n",Ans);
	return 0;
} 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值