题面传送门

我!竟!然!独!立!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. javascriptRemke之类的继承

    前言:es6之前在js中要实现继承,就必须要我们程序员在原型链上手动继承多对象的操作,但是结果往往存在漏洞,为解决这些问题,社区中出现了盗用构造函数.组合继承.原型式继承.寄生式继承等一系列继承方式, ...

  2. 《看漫画学Pyhton》中计算水仙花数

    利用while循环实现 i = 100 r = 0 s = 0 t = 0 while i < 1000: r = i // 100 s = (i - r * 100) // 10 t = i ...

  3. vue.$set实现原理

    上源码: export function set (target: Array<any> | Object, key: any, val: any): any { if (process. ...

  4. 《手把手教你》系列技巧篇(三十四)-java+ selenium自动化测试-单选和多选按钮操作-中篇(详解教程)

    1.简介 今天这一篇宏哥主要是讲解一下,如何使用list容器来遍历单选按钮.大致两部分内容:一部分是宏哥在本地弄的一个小demo,另一部分,宏哥是利用JQueryUI网站里的单选按钮进行实战. 2.d ...

  5. 如何获取ISO8601定义的Work Week

    工作中遇到一个需求,需要在打印标签的时候打印生产当天的工作周.工作周按照ISO-8601定义的标准计算.找到两种方案. Excel函数 C#代码 Excel函数 非常简单,调用一个Excel自带函数就 ...

  6. Scrum Meeting 0423

    零.说明 日期:2021-4-23 任务:简要汇报两日内已完成任务,计划后两日完成任务 一.进度情况 组员 负责 两日内已完成的任务 后两日计划完成的任务 qsy PM&前端 完成引导页UI# ...

  7. [Beta]the Agiles Scrum Meeting 2

    会议时间:2020.5.11 20:00 1.每个人的工作 今天已完成的工作 成员 已完成的工作 yjy 修复bug将自动评测改为异步HTTP请求 tq 实现查看.删除测试点功能的后端将自动评测改为异 ...

  8. Noip模拟10 2021.6.27

    T1 入阵曲 好了,又一个考试败笔题. 也就是在那个时候,小 F 学会了矩阵乘法.让两个矩阵乘几次就能算出斐波那契数, 真是奇妙无比呢. 不过, 小 F 现在可不想手算矩阵乘法--他觉得好麻烦.取而代 ...

  9. fatal error: sqlite3.h: No such file or directory

    编译带有sqlite3的数据库c语言程序时,出现fatal error: sqlite3.h: No such file or directory,找不到头文件的问题.应该是是系统没有安装函数库. 在 ...

  10. Luogu P1118 [USACO06FEB]数字三角形 Backward Digit Sums | 搜索、数学

    题目链接 思路:设一开始的n个数为a1.a2.a3...an,一步一步合并就可以用a1..an表示出最后剩下来的数,不难发现其中a1..an的系数恰好就是第n层杨辉三角中的数.所以我们可以先处理出第n ...