[WC2010]重建计划

重建计划

题解

看到这道题首先应该是很容易想到0/1分数规划的。毕竟那里明摆着一个 ∑ i = 1 k − 1 v a l i ∣ S ∣ \frac{\sum_{i=1}^{k-1}val_{i}}{|S|} Si=1k1vali
我们只需要将下面的分母当做 1 1 1即可,所以当二分到 m i d mid mid时,若 m i d mid mid可呗构造出来,有 ∑ i = 1 k − 1 v a l i ∣ S ∣ ≥ m i d ⟺ ∑ i = 1 k − 1 ( v a l i − m i d ) ≥ 0 \frac{\sum_{i=1}^{k-1}val_{i}}{|S|}\geq mid\Longleftrightarrow \sum_{i=1}^{k-1}(val_{i}-mid)\geq 0 Si=1k1valimidi=1k1(valimid)0
所以我们只需判断当前的 m i d mid mid跑出来的有无权值大于0的合法路径即可。

关键是怎么找这个路径。
由于它求的是任意点对 ( u , v ) (u,v) (u,v)的路径长度最大值,我们很快就想到了点分治。
我们可以通过点分治来找到这些路径的最大长度。
在一个点的子树里统计的时候,我们就先对它的每个儿子的子树跑bfs,找到它的儿子在 [ 0 , U ) [0,U) [0,U)的边数内,每种边数的最深距离,再像树dp一样进行合并,用 f i f_{i} fi表示深度为 i i i时的最长路径。
合并之前将两个未合并的部分与新求出来的部分一起统计答案,每个 f f f对应的区间的 b f s bfs bfs序一定是连续的,我们可以用单调队列来求出最大值,这个过程时间复杂度是 O ( 子树大小 ) O\left(子树大小\right) O(子树大小)的。

由于点分治最多只会有 l o g n log_{n} logn层,所以每次判读时间复杂度是 O ( n l o g   n ) O\left(nlog\,n\right) O(nlogn)的。
由于点分治的过程是一直沿着重心下去的,我们可以先预处理重心的顺序,这样分治每次判断时就不用单独处理了。

总时间复杂度 O ( n l o g 2   n ) O\left(nlog^2\,n\right) O(nlog2n)

源码

#include<bits/stdc++.h>
using namespace std;
#define MAXN 100005
#define reg register
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int,int> pii;
const int INF=0x7f7f7f7f;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
int n,L,U,head[MAXN],tot,mxson[MAXN],siz[MAXN],S,mx;
int root[MAXN],idx,sta[MAXN],stak,las,len[MAXN],q[MAXN];
double dis[MAXN],f[MAXN];bool vis[MAXN],vp[MAXN],flag;
struct ming{int u,v,w;}a[MAXN];
struct edge{int to,nxt;double paid;}e[MAXN<<1];
void addEdge(int u,int v,double w){e[++tot]=(edge){v,head[u],w};head[u]=tot;}
void bfs(int st){
	vp[sta[++stak]=st]=1;
	for(int i=las+1;i<=stak;i++)
		for(int j=head[sta[i]];j;j=e[j].nxt){
			int v=e[j].to;if(vis[v]||vp[v])continue;
			dis[v]=dis[sta[i]]+e[j].paid;
			len[v]=len[sta[i]]+1;vp[sta[++stak]=v]=1;
		}
	for(int i=las+1;i<=stak;i++)vp[sta[i]]=0;
}
void check(){
	int head=1,tail=0,id=las+1;
	for(int i=min(U,len[sta[stak]]);i>=0;i--){
		int tl=max(L-i,0),tr=U-i;
		while(head<=tail&&len[q[head]]<tl)head++;
		while(id<=stak&&len[sta[id]]<tl)++id;
		while(id<=stak&&len[sta[id]]<=tr){
			while(head<=tail&&dis[q[tail]]<=dis[sta[id]])tail--;
			q[++tail]=sta[id++];
		}
		if(head<=tail&&f[i]+dis[q[head]]>=0)return (void)(flag=1);
	}
}
void devide(int x){
	vis[x]=1;f[0]=dis[x]=len[x]=0;sta[++stak]=x;
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].to;if(vis[v])continue;
		las=stak;dis[v]=e[i].paid;len[v]=1;bfs(v);check();
		for(int j=las+1;j<=stak;j++)
			f[len[sta[j]]]=max(f[len[sta[j]]],dis[sta[j]]);
	}  
	while(stak)f[len[sta[stak--]]]=-INF;
	for(int i=head[x];i;i=e[i].nxt)if(!vis[e[i].to])devide(root[++idx]);
}
bool sakura(double mid){
	idx=tot=0;for(int i=1;i<=n;i++)head[i]=vis[i]=0;
	for(int i=1;i<n;i++)
		addEdge(a[i].u,a[i].v,1.0*a[i].w-mid),
		addEdge(a[i].v,a[i].u,1.0*a[i].w-mid);
	flag=0;devide(root[++idx]);return flag;
}
void getroot(int u,int fa){
	siz[u]=1;mxson[u]=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;if(v==fa||vis[v])continue;getroot(v,u);
		siz[u]+=siz[v];mxson[u]=max(siz[v],mxson[u]);
	}
	mxson[u]=max(mxson[u],S-siz[u]);
	if(mxson[u]<mx){mx=mxson[u];root[idx]=u;}
}
void dfs(int u){
	vis[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;if(vis[v])continue;
		S=siz[v];++idx;mx=INF;getroot(v,0);dfs(root[idx]);
	}
}
void init(){
	tot=0;for(int i=1;i<=n;i++)head[i]=0;
	for(int i=1;i<n;i++)addEdge(a[i].u,a[i].v,0),addEdge(a[i].v,a[i].u,0);
	mx=INF;S=n;++idx;getroot(1,0);dfs(root[idx]);
}
signed main(){
	read(n);read(L);read(U);
	for(int i=1;i<n;i++)read(a[i].u),read(a[i].v),read(a[i].w);
	for(int i=0;i<n;i++)f[i]=-INF;
	init();double l=0,r=1e6,times=40,Ans=r;
	while(times--){
		double mid=(l+r)/2.0;
		if(sakura(mid))l=mid,Ans=mid;
		else r=mid;
	}
	printf("%.3f\n",Ans);
	return 0;
}

谢谢!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值