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

31 篇文章 0 订阅
13 篇文章 1 订阅

题目大意:给定一棵树,询问长度在[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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: bzoj作为一个计算机竞赛的在线评测系统,不仅可以提供大量的题目供程序员练习和学习,还可以帮助程序员提升算法和编程能力。为了更好地利用bzoj进行题目的学习和刷题,制定一个bzoj做题计划是非常有必要的。 首先,我们需要合理安排时间,每天留出一定的时间来做bzoj的题目。可以根据自己的时间安排,每天挑选适量的题目进行解答。可以先从难度较低的题目开始,逐渐提高难度,这样既能巩固基础知识,又能挑战自己的思维能力。 其次,要有一个计划和目标。可以规划一个每周或每月的题目数量目标,以及每个阶段要学习和掌握的算法知识。可以根据bzoj的题目分类,如动态规划、图论、贪心算法等,结合自己的实际情况,有针对性地选择题目进行学习。 此外,要充分利用bzoj提供的资源。bzoj网站上有很多高质量的题解和优秀的解题代码,可以参考和学习。还有相关的讨论区,可以与其他程序员交流和讨论,共同进步。 最后,要坚持并保持思考。做题不是单纯为了刷数量,更重要的是学会思考和总结。遇到难题时,要有耐心,多思考,多尝试不同的解法。即使不能一次性解出来,也要学会思考和分析解题过程,以及可能出现的错误和优化。 总之,bzoj做题计划的关键在于合理安排时间、制定目标、利用资源、坚持思考。通过有计划的刷题,可以提高算法和编程能力,并培养解决问题的思维习惯,在计算机竞赛中取得更好的成绩。 ### 回答2: bzoj做题计划是指在bzoj这个在线测评系统上制定一套学习和刷题的计划,并且将计划记录在excel表格中。该计划主要包括以下几个方面的内容。 首先是学习目标的设定。通过分析自己的水平和知识缺口,可以设定一个合理的目标,比如每天解决一定数量的题目或者提高特定的算法掌握程度。 其次是题目选择的策略。在excel表格中可以记录下自己选择的题目编号、题目类型和难度等信息。可以根据题目的类型和难度来安排每天的刷题计划,确保自己可以逐步提高技巧和解题能力。 然后是学习进度的记录和管理。将每天的完成情况记录在excel表格中,可以清晰地看到自己的学习进度和任务完成情况。可以使用图表等功能来对学习进度进行可视化展示,更好地管理自己的学习计划。 同时,可以在excel表格的备注栏中记录下每道题目的解题思路、关键和需要复习的知识等信息。这样可以方便自己回顾和总结,巩固所学的知识。 最后,可以将excel表格与其他相关资料进行整合,比如算法教材、题目解析和学习笔记等。这样可以形成一个完整的学习档案,方便自己进行系统的学习和复习。 总之,bzoj做题计划excel的制定和记录可以帮助我们更加有条理和高效地进行学习和刷题。通过合理安排学习目标和题目选择策略,记录学习进度和思路,并整合其他学习资料,我们可以提高自己的解题能力,并在bzoj上取得更好的成绩。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值