BZOJ 1758 Wc2010 重建计划 树的点分治+二分+单调队列

题目大意:给定一棵树,询问长度在[l,u]范围内的路径中边权的平均值的最大值

01分数规划,首先想到二分答案

既然是统计路径肯定是点分治

每次统计时我们要找有没有大于0的路径存在

那么对于一棵子树的每一个深度i记录一个路径权值和的最大值

然后在这棵子树之前的所有子树的深度可选范围就是[l-i,u-i] 这个窗口是不停滑动的 因此用单调队列维护最大值即可

↑上面这些网上的题解都说的还是蛮详细的

据说二分套在树分治里面会快一些 但是 尼玛 我被卡常了!!

这里只提供一些剪枝

1.当前分治的总点数<=l 那么一定不能找到一条长度>=l的路径 直接退出即可

2.二分的下界可以设为ans 二分后直接将ans设为l

3.避免一切的memset 利用时间戳来优化

4.一个奇葩的优化:由于剪枝2那里l那里有剪枝 因此二分的时候可以偏向l一些 比如说令mid=(l+l+r)/3 亲测当mid=(l*9+r)/10的时候是最快的

为何乃们都写的那么快……为何我被卡常了……QAQ

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define M 100100
using namespace std;
struct abcd{
	int to,f,next;
	bool ban;
}table[M<<1];
int head[M],tot=1;
int n,m,lower,upper;
int size[M],fa[M];
double ans;
namespace IStream{
    const int L=1<<15;
    char buffer[L];
    char *S,*T;
    inline char Get_Char()
    {
        if(S==T)
        {
            T=(S=buffer)+fread(buffer,1,L,stdin);
            if(S==T) return EOF;
        }
        return *S++;
    }
    inline int Get_Int()
    {
        int re=0;
        char c;
        do c=Get_Char(); while(c<'0'||c>'9');
        while(c>='0'&&c<='9')
            re=(re<<1)+(re<<3)+(c-'0'),c=Get_Char();
        return re;
    }
}
void Add(int x,int y,int z)
{
	table[++tot].to=y;
	table[tot].f=z;
	table[tot].next=head[x];
	head[x]=tot;
}
void Get_Centre_Of_Gravity(int x,int n,int from,int &cg)
{
	int i,flag=1;
	size[x]=1;fa[x]=from;
	for(i=head[x];i;i=table[i].next)
		if(!table[i].ban&&table[i].to!=from)
		{
			Get_Centre_Of_Gravity(table[i].to,n,x,cg);
			size[x]+=size[table[i].to];
			if(size[table[i].to]<<1>n) flag=0;
		}
	if(n-size[x]<<1>n) flag=0;
	if(flag) cg=x;
}
void DFS(int x,int from,double avg,double now,int dpt,double temp[],int& max_dpt)
{
	int i;
	if(dpt>max_dpt)
	{
		temp[dpt]=now;
		max_dpt=dpt;
	}
	else temp[dpt]=max(temp[dpt],now);
	for(i=head[x];i;i=table[i].next)
		if(!table[i].ban&&table[i].to!=from)
			DFS(table[i].to,x,avg,now+table[i].f-avg,dpt+1,temp,max_dpt);
}
bool Judge(int cg,double avg)
{
	static double max_f[M];
	static int tim[M],T;
	static double temp[M];
	int i;
	max_f[0]=0;tim[0]=++T;
	for(i=head[cg];i;i=table[i].next)
		if(!table[i].ban)
		{
			int max_dpt=0;
			DFS(table[i].to,cg,avg,table[i].f-avg,1,temp,max_dpt);
			static int q[M];
			int j,k=max(0,lower-max_dpt),r=0,h=0;
			for(j=max_dpt;j;j--)
			{
				for(;j+k<=upper;k++)
				{
					if(tim[k]!=T) tim[k]=T,max_f[k]=-2147483647;
					while(r>h&&max_f[k]>max_f[q[r]])
						q[r--]=0;
					q[++r]=k;
				}
				while(r>h&&q[h+1]+j<lower)
					q[++h]=0;
				if(max_f[q[h+1]]+temp[j]>=0)
					return true;
			}
			for(j=max_dpt;j;j--)
				max_f[j]=max(max_f[j],temp[j]);
		}
	return false;
}
void Tree_Divide_And_Conquer(int root,int size)
{
	int i,cg;
	if(size<=lower) return ;
	Get_Centre_Of_Gravity(root,size,0,cg);
	double l=ans,r=m;
	while(r-l>1e-4)
	{
		double mid=(l*9+r)/10.0;
		if( Judge(cg,mid) )
			l=mid;
		else
			r=mid;
	}
	ans=l;
	if(fa[cg]) ::size[fa[cg]]=size-::size[cg];
	for(i=head[cg];i;i=table[i].next)
		if(!table[i].ban)
		{
			table[i].ban=table[i^1].ban=1;
			Tree_Divide_And_Conquer(table[i].to,::size[table[i].to]);
		}
}
int main()
{

	//freopen("rebuild.in","r",stdin);
	//freopen("rebuild.out","w",stdout);

	int i,x,y,z;
	cin>>n>>lower>>upper;
	for(i=1;i<n;i++)
	{
		//scanf("%d%d%d",&x,&y,&z);
		x=IStream::Get_Int();
		y=IStream::Get_Int();
		z=IStream::Get_Int();
		Add(x,y,z);
		Add(y,x,z);
		m=max(m,z);
	}
	Tree_Divide_And_Conquer(1,n);
	printf("%.3lf\n",ans);
	return 0;
}
时间: 2024-08-01 14:10:44

BZOJ 1758 Wc2010 重建计划 树的点分治+二分+单调队列的相关文章

bzoj 1758 [Wc2010]重建计划 分数规划+树分治单调队列check

[Wc2010]重建计划 Time Limit: 40 Sec  Memory Limit: 162 MBSubmit: 4345  Solved: 1054[Submit][Status][Discuss] Description Input 第一行包含一个正整数N,表示X国的城市个数. 第二行包含两个正整数L和U,表示政策要求的第一期重建方案中修建道路数的上下限 接下来的N-1行描述重建小组的原有方案,每行三个正整数Ai,Bi,Vi分别表示道路(Ai,Bi),其价值为Vi 其中城市由1..N

[BZOJ]1758: [Wc2010]重建计划

题目大意:给定一棵n个点的带边权的树和l,u,求长度在[l,u]之间平均权值最大的链的权值.(n<=100,000) 思路:二分答案,把树上每条边减去二分出的答案,点分治check是否有长度在[l,u]之间权值和大等0的链,每次把每棵子树按深度排序,记下各个深度到根距离最大的节点,再用单调队列统计即可,总复杂度O(nlogn^2).此题卡常差评,重心只要算一次,不用每次二分都算,稍微卡卡就过了. #include<cstdio> #include<cstring> #incl

【BZOJ 1758】 [Wc2010]重建计划

1758: [Wc2010]重建计划 Time Limit: 40 Sec  Memory Limit: 162 MB Submit: 989  Solved: 345 [Submit][Status] Description Input 第一行包含一个正整数N,表示X国的城市个数. 第二行包含两个正整数L和U,表示政策要求的第一期重建方案中修建道路数的上下限 接下来的N-1行描述重建小组的原有方案,每行三个正整数Ai,Bi,Vi分别表示道路(Ai,Bi),其价值为Vi 其中城市由1..N进行标

hdu 4123 Bob’s Race (树的直径相关+rmq+单调队列思想)

Bob's Race Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 2115    Accepted Submission(s): 658 Problem Description Bob wants to hold a race to encourage people to do sports. He has got trouble

【bzoj1758】[Wc2010]重建计划

Description Input 第一行包含一个正整数N,表示X国的城市个数. 第二行包含两个正整数L和U,表示政策要求的第一期重建方案中修建道路数的上下限 接下来的N-1行描述重建小组的原有方案,每行三个正整数Ai,Bi,Vi分别表示道路(Ai,Bi),其价值为Vi 其中城市由1..N进行标号 Output 输出最大平均估值,保留三位小数 Sample Input 4 2 3 1 2 1 1 3 2 1 4 3 Sample Output 2.500 HINT N<=100000,1<=L

bzoj1758 [Wc2010]重建计划

Description Input 第一行包含一个正整数N,表示X国的城市个数. 第二行包含两个正整数L和U,表示政策要求的第一期重建方案中修建道路数的上下限 接下来的N-1行描述重建小组的原有方案,每行三个正整数Ai,Bi,Vi分别表示道路(Ai,Bi),其价值为Vi 其中城市由1..N进行标号 Output 输出最大平均估值,保留三位小数 Sample Input 4 2 3 1 2 1 1 3 2 1 4 3 Sample Output 2.500 HINT N<=100000,1<=L

bzoj1758 [Wc2010]重建计划 &amp; bzoj2599 [IOI2011]Race

两题都是树分治. 1758这题可以二分答案avgvalue,因为avgvalue=Σv(e)/s,因此二分后只需要判断Σv(e)-s*avgvalue是否大于等于0,若大于等于0则调整二分下界,否则调整二分上界.假设一棵树树根为x,要求就是经过树根x的最大答案,不经过树根x的可以递归求解.假设B[i]为当前做到的一颗x的子树中的点到x的距离为i的最大权值,A[i]为之前已经做过的所有子数中的点到x的距离为i的最大权值(这里的权值是Σv(e)-i*avgvalue),那么对于当前子树的一个距离i,

BZOJ1758: [Wc2010]重建计划(01分数规划+点分治+单调队列)

题目:http://www.lydsy.com/JudgeOnline/problem.php?id=1758 01分数规划,所以我们对每个重心进行二分.于是问题转化为Σw[e]-mid>=0, 对于一棵子树维护点的dep,dis,并用队列q存下来.令mx[i]表示当前dep为i的最大权值,维护一个单调队列dq,维护当前符合条件的mx,当我们从q的队尾向前扫时,它的dep是递减的,利用这个性质维护单调队列,最后更新一遍mx.具体看代码吧TAT 代码: #include<cstring>#

P4292 [WC2010]重建计划

传送门 首先这玩意儿很明显是分数规划,二分一个答案\(mid\),边权变为\(w_i-mid\),然后看看能不能找到一条路径长度在\([L,R]\)之间,且边权总和非负,这个可以转化为求一条满足条件的边权最大的路径 这个实际上可以用点分做,用单调队列可以优化到\(O(nlog^2n)\),然而我不知道为什么写挂掉了所以这里就不说了-- 我们设\(f_{i,j}\)表示以\(i\)为根的子树中,从\(i\)向下走\(j\)步的链最长是多少.转移可以用长链剖分优化到均摊\(O(1)\).查询答案的话