bzoj1758+WC2010

题目大意,给出一棵树,有边权,找出其中一条包含了不少于L,不多于R条边的路径,使得  Average(v(e)) 最大,上式表示所有选择的边的平均权值。

(n<=50000,边权<=10^7)

思路:因为是平均数所以想到了二分(其实根本想不到),我们二分最后的答案,将树上所有边都减去当前二分到的的平均数,然后再在树中找有没有一条包含了不少于L,不多于R条边的路径,使得路径总长>=0了,如果有,说明当前答案小了,否则当前答案大了。

对于二分答案的的验证,我们采用点分治,对于每一个当前子树(设为s),我们设G(s)为当前子树的重心,son(s)为儿子的集合,那么对于每一个状态s,我们都必须计算出以s根为转折点的最长路径(当然要满足限制,之后不赘述了),那么我们这样考虑,对于当前s,我们找到它的重心,设为当前的root,那么这样以后树的深度就不会超过log层了,我们对于当前的每个son(root),找到它到根的很多条链,我们设g(k)为son中,包含了k条边的最长的链长度,然后合并到当前root的状态上。(合并的过程用单调队列实现)。

那么复杂度为什么是n*log的呢?我们想到对于深度为1的root,我们需要进行o(n)的操作(包括找重心,找链,单调队列合并(虽然常数大但是确实是o(n)的)),那么第二层一共也最多加起来进行o(n)的操作,因为一共log层,所以不超过n*log的,常熟巨大就是了。

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <string>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <ctime>
using namespace std;
struct node{int to;int next;double len;
};node bian[400010];
int sum = 0,first[400010],size[400010],leng,lenf,max_weight,l,r;
int weight,q[400010],n,a,b;
double Mina,g[400010],f[400010],c;
bool visit[400010],found;
void inser(int x,int y,double z) {
    bian[ ++ sum].to = y;
    bian[sum].next = first[x];
    bian[sum].len = z;
    first[x] = sum;
}
void dfs(int x,int Anc) {
	size[x] = 1;
	for(int u = first[x];u;u = bian[u].next)
	    if(!visit[bian[u].to] && bian[u].to != Anc)
	    {
	    	dfs(bian[u].to,x);
	    	size[x] += size[bian[u].to];
	    }
	return;
}
void bfs(int x,int Anc,int depth,double val) {
	if(depth > leng) leng = depth,g[leng] = -1000000000000;
	if(val > g[depth]) g[depth] = val;
	for(int u = first[x];u;u = bian[u].next)
	    if(!visit[bian[u].to] && bian[u].to != Anc)
	    	bfs(bian[u].to,x,depth + 1,val + bian[u].len - Mina);
}
void find_weight(int x,int Anc,int F) {
	int ret = 0;
	int sum = 0;
	for(int u = first[x];u;u = bian[u].next)
	    if(!visit[bian[u].to] && bian[u].to != Anc)
	    {
	    	find_weight(bian[u].to,x,F);
	    	if(size[bian[u].to] > ret)
	    	    ret = size[bian[u].to];
	    	sum += size[bian[u].to];
	    }
	if(size[F] - sum > ret) ret = size[F] - sum;
	if(ret < max_weight) weight = x,max_weight = ret;
} 
void Div(int x,int Anc) {
	dfs(x,Anc);
	if(l > size[x]) return ;
	max_weight = 10000000;
	find_weight(x,Anc,x);
	dfs(weight,weight);
    for(int i = 0;i <= size[weight];i ++) f[i] = g[i] = -1000000000000;
    lenf = 0;
    for(int u = first[weight];u;u = bian[u].next)
        if(!visit[bian[u].to] && bian[u].to != Anc)
        {
        	leng = 0;
        	bfs(bian[u].to,weight,1,bian[u].len - Mina);
        	int head = 1,tail = 0,last = leng;
        	for(int i = 0;i <= lenf;i ++)
        	{
        		while(last + i >= l && last >= 0) 
        		{        		
				    while(head <= tail && q[head] + i > r) head ++;
        		    while(head <= tail && g[last] > g[q[tail]]) tail --;
				    q[ ++ tail] = last;
				    last --;
        		}
				if(head <= tail && g[q[head]] + f[i] > 0) { found = true;return ;} 
        	}
        	lenf = max(lenf,leng);
        	for(int i = 0;i <= leng;i ++) 
        	    if(g[i] > f[i])
        	    	f[i] = g[i];
        }
    visit[weight] = true;
    for(int u = first[weight];u;u = bian[u].next)
        if(!visit[bian[u].to] && bian[u].to != Anc && found == false) 
            Div(bian[u].to,weight);
}
bool check(double x) {
	memset(visit,false,sizeof(visit));
	Mina = x;
	found = false;
	Div(1,0);
	return found;
}
int main() {
	scanf("%d",&n);
	scanf("%d%d",&l,&r);
	for(int i = 1;i < n;i ++) 
	{
		scanf("%d%d",&a,&b);
		scanf("%lf",&c);
		inser(a,b,c);
		inser(b,a,c);
	}
	double head = 0,tail = 1000000.0;
	while(tail - head > 0.0001)
	{
		double Mid = (head + tail) / 2.0;
		if(check(Mid)) head = Mid;
		else tail = Mid;
	}
	printf("%0.3f",head);
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值