题面传送门

我!竟!然!独!立!A!C!了!这!道!题!incredible!

首先看到这类最大化某个分式的题目,可以套路地想到分数规划,考虑二分答案 \(mid\) 并检验是否存在合法的 \(S\) 使得 \(\dfrac{\sum\limits_{e\in S}v(e)}{|S|}\ge mid\),将分母乘过去并稍微变个形可得 \(\sum\limits_{e\in S}v(e)-mid\ge 0\),也就是说我们将每条边边权都减去 \(mid\) 并检验包含 \([L,R]\) 条边的路径权值和的最大值是否 \(\ge 0\),于是现在题目转化为怎样求包含 \([L,R]\) 条边路径权值和的最大值。

我们记 \(f_{x,i}\) 为对于 \(x\) 的子树中所有深度为 \(i\) 的点 \(y\),\(x\) 到 \(y\) 距离的最大值,那么显然有状态转移方程 \(dp_{x,i}=\max\limits_{y\in son(x)}dp_{y,i}+v(x,y)\),其中 \(v(x,y)\) 为 \(x,y\) 之间边的边权,显然我们可以像点分治那样在遍历子树,更新 \(f_{x,i}\) 的过程中更新答案。但暴力遍历更新是 \(\mathcal O(n^2)\) 的,无法通过。考虑优化,这时候长链剖分就要派上用场了,我们建一棵全局线段树,实时维护当前 \(f_{x,i}\) 的值,我们遍历深儿子 \(y\) 的时候就直接将其 \(f_{y,i}\) 的值继承到 \(f_{x,i}\) 上,当然由于贡献中有一个 \(v(x,y)\),因此 \(f_{x,i}\) 的值应当为 \(f_{y,i}\) 加上 \(v(x,y)\),这个可以通过线段树区间加解决,即在线段树上 \([dep_x+1,mxdep_x]\) 的值全部加上 \(v(x,y)\)。遍历浅儿子 \(u\)的时候,我们就在第一次遍历时开个临时变量(vector<vector<double> >)记录下所有 \(f_{u,j}\) 的值——显然对于每个浅儿子 \(u\),\(f_{u,j}\) 只在 \(j\in[dep_u,mxdep_u]\) 时值不是 \(-\infty\),因此我们只需记录下 \(mxdep_u-dep_u+1\) 个这样的值即可,根据所有链长之和为线性可知对于所有 \(x\),我们总共最多保存 \(\mathcal O(n)\) 个这样的值。然后第二遍遍历时直接调用之前保存下来的值,在线段树上单点修改即可,贡献就随便乱搞搞,算一下即可,相当于在线段树上执行一遍区间查询。

总复杂度 \(n\log^2n\),其中一个 \(\log\) 来源于线段树,一个 \(\log\) 来源于分数规划中的二分。

代码可能要开个 O2 才能过:

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=1e5;
const double INF=1e12;
const double EPS=1e-4;
int n,L,R,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],val[MAXN*2+5],ec=0;
void adde(int u,int v,int w){to[++ec]=v;val[ec]=w;nxt[ec]=hd[u];hd[u]=ec;}
struct node{int l,r;double mx,lz;} s[MAXN*4+5];
void pushup(int k){s[k].mx=max(s[k<<1].mx,s[k<<1|1].mx);}
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;s[k].mx=-INF;s[k].lz=0;if(l==r) return;
int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
}
void pushdown(int k){
if(fabs(s[k].lz)>EPS){
s[k<<1].lz+=s[k].lz;s[k<<1].mx+=s[k].lz;
s[k<<1|1].lz+=s[k].lz;s[k<<1|1].mx+=s[k].lz;
s[k].lz=0;
}
}
void add(int k,int l,int r,double v){
if(l<=s[k].l&&s[k].r<=r){s[k].mx+=v;s[k].lz+=v;return;}
pushdown(k);int mid=s[k].l+s[k].r>>1;
if(r<=mid) add(k<<1,l,r,v);
else if(l>mid) add(k<<1|1,l,r,v);
else add(k<<1,l,mid,v),add(k<<1|1,mid+1,r,v);
pushup(k);
}
void modify(int k,int p,double x){
if(s[k].l==s[k].r){chkmax(s[k].mx,x);return;}
pushdown(k);int mid=s[k].l+s[k].r>>1;
if(p<=mid) modify(k<<1,p,x);
else modify(k<<1|1,p,x);
pushup(k);
}
void clear(int k,int p){
if(s[k].l==s[k].r){s[k].mx=-INF;return;}
pushdown(k);int mid=s[k].l+s[k].r>>1;
if(p<=mid) clear(k<<1,p);
else clear(k<<1|1,p);
pushup(k);
}
double query(int k,int l,int r){
if(l<=s[k].l&&s[k].r<=r) return s[k].mx;
pushdown(k);int mid=s[k].l+s[k].r>>1;
if(r<=mid) return query(k<<1,l,r);
else if(l>mid) return query(k<<1|1,l,r);
else return max(query(k<<1,l,mid),query(k<<1|1,mid+1,r));
}
int dep[MAXN+5],mxdep[MAXN+5],dson[MAXN+5],ed[MAXN+5];
void dfs0(int x=1,int f=0){
for(int e=hd[x];e;e=nxt[e]){
int y=to[e],z=val[e];if(y==f) continue;
dep[y]=mxdep[y]=dep[x]+1;dfs0(y,x);
if(mxdep[y]>mxdep[x]) mxdep[x]=mxdep[y],dson[x]=y,ed[x]=z;
}
}
double ans=-INF;
void dfs(int x,int f,double v){
vector<pair<int,vector<double> > > t;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e],z=val[e];if(y==f||y==dson[x]) continue;
dfs(y,x,v);vector<double> tmp;
for(int j=dep[y];j<=mxdep[y];j++) tmp.pb(query(1,j,j)),clear(1,j);
t.pb(mp(z,tmp));
} if(dson[x]){
dfs(dson[x],x,v);
add(1,dep[x]+1,mxdep[x],ed[x]-v);
} chkmax(ans,query(1,min(dep[x]+L,n),min(dep[x]+R,n)));
modify(1,dep[x],0);
for(int i=0;i<t.size();i++){
for(int j=0;j<t[i].se.size();j++){
double vv=t[i].fi-v+t[i].se[j];
if(R-j-1<0) continue;
int lwb=max(L-j-1,0),upb=R-j-1;
chkmax(ans,query(1,min(dep[x]+lwb,n),min(dep[x]+upb,n))+vv);
}
for(int j=0;j<t[i].se.size();j++)
modify(1,dep[x]+1+j,t[i].fi-v+t[i].se[j]);
}
}
bool check(double mid){
build(1,0,n);ans=-INF;dfs(1,0,mid);
// printf("%.3lf\n",ans);
return ans>-EPS;
}
int main(){
scanf("%d%d%d",&n,&L,&R);
for(int i=1,u,v,w;i<n;i++){
scanf("%d%d%d",&u,&v,&w);
adde(u,v,w);adde(v,u,w);
} dfs0();//check(2.5);
double l=0,r=1e6,x=-114514.1919810;
while(fabs(r-l)>EPS){
double mid=(l+r)/2.0;
if(check(mid)) x=mid,l=mid;
else r=mid;
} printf("%.3lf\n",x);
return 0;
}

洛谷 P4292 - [WC2010]重建计划(长链剖分+线段树)的更多相关文章

  1. 洛谷 P4292 [WC2010]重建计划 解题报告

    P4292 [WC2010]重建计划 题目描述 \(X\)国遭受了地震的重创, 导致全国的交通近乎瘫痪,重建家园的计划迫在眉睫.\(X\)国由\(N\)个城市组成, 重建小组提出,仅需建立\(N-1\ ...

  2. [WC2010]重建计划 长链剖分

    [WC2010]重建计划 LG传送门 又一道长链剖分好题. 这题写点分治的人应该比较多吧,但是我太菜了,只会长链剖分. 如果你还不会长链剖分的基本操作,可以看看我的长链剖分总结. 首先一看求平均值最大 ...

  3. 「WC2010」重建计划(长链剖分/点分治)

    「WC2010」重建计划(长链剖分/点分治) 题目描述 有一棵大小为 \(n\) 的树,给定 \(L, R\) ,要求找到一条长度在 \([L, R]\) 的路径,并且路径上边权的平均值最大 \(1 ...

  4. BZOJ.1758.[WC2010]重建计划(分数规划 点分治 单调队列/长链剖分 线段树)

    题目链接 BZOJ 洛谷 点分治 单调队列: 二分答案,然后判断是否存在一条长度在\([L,R]\)的路径满足权值和非负.可以点分治. 对于(距当前根节点)深度为\(d\)的一条路径,可以用其它子树深 ...

  5. BZOJ1758[Wc2010]重建计划——分数规划+长链剖分+线段树+二分答案+树形DP

    题目描述 输入 第一行包含一个正整数N,表示X国的城市个数. 第二行包含两个正整数L和U,表示政策要求的第一期重建方案中修建道路数的上下限 接下来的N-1行描述重建小组的原有方案,每行三个正整数Ai, ...

  6. 2019.01.21 bzoj1758: [Wc2010]重建计划(01分数规划+长链剖分+线段树)

    传送门 长链剖分好题. 题意简述:给一棵树,问边数在[L,R][L,R][L,R]之间的路径权值和与边数之比的最大值. 思路: 用脚指头想都知道要01分数规划. 考虑怎么checkcheckcheck ...

  7. [WC2010]重建计划(长链剖分+线段树+分数规划)

    看到平均值一眼分数规划,二分答案mid,边权变为w[i]-mid,看是否有长度在[L,R]的正权路径.设f[i][j]表示以i为根向下j步最长路径,用长链剖分可以优化到O(1),查询答案线段树即可,复 ...

  8. BZOJ.3653.谈笑风生(长链剖分/线段树合并/树状数组)

    BZOJ 洛谷 \(Description\) 给定一棵树,每次询问给定\(p,k\),求满足\(p,a\)都是\(b\)的祖先,且\(p,a\)距离不超过\(k\)的三元组\(p,a,b\)个数. ...

  9. 2018牛客网暑假ACM多校训练赛(第七场)I Tree Subset Diameter 动态规划 长链剖分 线段树

    原文链接https://www.cnblogs.com/zhouzhendong/p/NowCoder-2018-Summer-Round7-I.html 题目传送门 -  https://www.n ...

随机推荐

  1. 成功在Caterpillar代码中插入事件对象-20200917

    首先搞清楚了Caterpillar的solidity代码生成机制.Caterpillar分为Caterpillar Core和 executepanel两部分. executePanel是UI前端,用 ...

  2. 【UE4 C++】Print、Delay、ConsoleCommand

    基于UKismetSystemLibrary PrintString /** * Prints a string to the log, and optionally, to the screen * ...

  3. kivy画板

    from kivy.app import App from kivy.graphics import Line, Color # 引入绘图线条,颜色 from kivy.uix.widget impo ...

  4. Java:ConcurrentHashMap类小记-1(概述)

    Java:ConcurrentHashMap类小记-1(概述) 对 Java 中的 ConcurrentHashMap类,做一个微不足道的小小小小记,分三篇博客: Java:ConcurrentHas ...

  5. MySQL:提高笔记-1

    MySQL:提高笔记-1 学完基础的语法后,进一步对 MySQL 进行学习 说明:这是根据 bilibili 上 黑马程序员 的课程 mysql入门到精通 后做的笔记 1. 索引 1.1 索引概述 M ...

  6. elasticsearch地理位置查询

    elasticsearch地理位置查询 一.背景 二.geo数据类型 1.geo_point 2.geo_shape 三.此处对geo_point类型实战 1.背景 2.插入地点数据 1.创建索引 2 ...

  7. 设计的MOS管三极管简单开关电路驱动能力不够1

    您需要 登录 才可以下载或查看,没有帐号?注册 x . ?& P' U5 r/ ~& `: B 用AOD409设计的开关电路为什么驱动能力不够,请大家帮忙分析一下原因啊.这个电路作用就 ...

  8. 查找最小生成树:克鲁斯克尔算法(Kruskal)算法

    一.算法介绍 Kruskal算法是一种用来查找最小生成树的算法,由Joseph Kruskal在1956年发表.用来解决同样问题的还有Prim算法和Boruvka算法等.三种算法都是贪心算法的应用.和 ...

  9. SVN查看项目修改记录及修改内容

    工具/原料 svn 一,查看修改记录 1 选择要查看的文件夹,打开之后在空白的地方右键. 2 选择svn里面的"查看日志".show_Log 3 在弹出的日志框里,可以看到,你可以 ...

  10. HTML 罗盘式时钟

    代码块: 1 <!DOCTYPE html> 2 <html lang="zh-hans"> 3 <head> 4 <meta chars ...