题目链接 BZOJ

洛谷

点分治 单调队列:

二分答案,然后判断是否存在一条长度在\([L,R]\)的路径满足权值和非负。可以点分治。

对于(距当前根节点)深度为\(d\)的一条路径,可以用其它子树深度在\([L-d,R-d]\)内的最大值更新。这可以用单调队列维护。

这需要子树中的点按dep排好序。可以用BFS,省掉sort。

直接这样的话,每次用之前的子树更新当前子树时,每次复杂度是\(O(\max\{dep\})\)的(之前子树中最大的深度)。能被卡成\(O(n^2\log n)\)。

可以再对每个点的所有子树按最大深度排序,从小的开始计算,这样复杂度就还是\(O(\sum dep)\)。总复杂度\(O(n\log n\log v)\)。

但是常数也比较大。

在二分前要先将点分树建出来(直接用vector存每个点作为根时它的整棵子树就行了)。

二分边界最好优化下。

最长链的2倍不足L就跳过。优化很明显...(8400->5500)

//36744kb	5548ms(没有O2用一堆vector就是慢...)
#include <cstdio>
#include <cctype>
#include <vector>
#include <algorithm>
//#define gc() getchar()
#define MAXIN 200000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
#define eps 1e-9
typedef long long LL;
const int N=1e5+5;
const double INF=1ll<<60; int L,R,Enum,H[N],nxt[N<<1],to[N<<1],len[N<<1],Min,root,sz[N];
bool vis[N];
char IN[MAXIN],*SS=IN,*TT=IN;
struct Node
{
int dep; LL dis;
};
struct Block//每个连通块
{
int mxd;
std::vector<Node> vec;
bool operator <(const Block &x)const
{
return mxd<x.mxd;
}
};
std::vector<Block> bl[N]; inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now;
}
inline void AE(int u,int v,int w)
{
to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum, len[Enum]=w;
to[++Enum]=u, nxt[Enum]=H[v], H[v]=Enum, len[Enum]=w;
}
void Find_root(int x,int fa,int tot)
{
int mx=0; sz[x]=1;
for(int i=H[x],v; i; i=nxt[i])
if(!vis[v=to[i]]&&v!=fa)
Find_root(v,x,tot), sz[x]+=sz[v], mx=std::max(mx,sz[v]);
mx=std::max(mx,tot-sz[x]);
if(mx<Min) Min=mx, root=x;
}
void BFS(int s,int rt,int sl)
{
static int q[N],pre[N],dep[N];
static LL dis[N];
int h=0,t=0; q[t++]=s, pre[s]=rt, dep[s]=1, dis[s]=sl;
bl[rt].push_back(Block());
std::vector<Block>::iterator it=--bl[rt].end();
while(h<t)
{
int x=q[h++]; it->vec.push_back((Node){dep[x],dis[x]});
for(int i=H[x],v; i; i=nxt[i])
if(!vis[v=to[i]]&&v!=pre[x])
pre[v]=x, dep[v]=dep[x]+1, dis[v]=dis[x]+len[i], q[t++]=v;
}
it->mxd=dep[q[h-1]];
std::reverse(it->vec.begin(),it->vec.end());//dep从大到小 保证匹配区间是递增的(递减的话边界不好找吧)。
}
void Solve(int x)
{
vis[x]=1;
for(int i=H[x],v; i; i=nxt[i])
if(!vis[v=to[i]]) BFS(v,x,len[i]);
std::sort(bl[x].begin(),bl[x].end());
for(int i=H[x],v; i; i=nxt[i])
if(!vis[v=to[i]]) Min=N, Find_root(v,x,sz[v]), Solve(root);
}
void Init(int n)
{
Min=N, Find_root(1,1,n), Solve(root);
}
bool Check(int n,double X)
{
static int q[N];
static double mx[N];
for(int i=1; i<=n; ++i) mx[i]=-INF;
std::vector<Node>::iterator it2,ed2;
std::vector<Block>::iterator it1,ed1;
for(int s=1; s<=n; ++s)
{
if(!bl[s].size()||2*(--bl[s].end())->mxd<L) continue;//最长链的2倍不足L就跳过。优化很明显...
bool vic=0;
for(it1=bl[s].begin(),ed1=bl[s].end(); it1!=ed1; ++it1)
{
int mxd=it1->mxd,now=1,h=1,t=0;
for(it2=it1->vec.begin(),ed2=it1->vec.end(); it2!=ed2; ++it2)//用当前子树的值和之前子树的链匹配
{
int l=std::max(0,L-it2->dep),r=std::min(mxd,R-it2->dep);//当前链能匹配的路径区间
if(l>r) continue;
while(now<=r)
{
while(h<=t && mx[q[t]]<mx[now]) --t;
q[++t]=now++;
}
while(h<=t && q[h]<l) ++h;
if(mx[q[h]]+it2->dis-X*it2->dep>eps) {vic=1; goto Skip;}
}
for(it2=it1->vec.begin(),ed2=it1->vec.end(); it2!=ed2; ++it2)//用当前子树更新状态,顺便判断一下是否有到根节点的满足条件的路径。
{
int d=it2->dep; mx[d]=std::max(mx[d],it2->dis-X*d);
if(L<=d && d<=R && mx[d]>eps) {vic=1; goto Skip;}
}
}
Skip: ;
for(it1=bl[s].begin(),ed1=bl[s].end(); it1!=ed1; ++it1)
for(it2=it1->vec.begin(),ed2=it1->vec.end(); it2!=ed2; ++it2)
mx[it2->dep]=-INF;
if(vic) return 1;
}
return 0;
} int main()
{
int n=read(),mn=1e6,mx=0; L=read(),R=read();
for(int i=1,u,v,w; i<n; ++i) u=read(),v=read(),w=read(),AE(u,v,w),mn=std::min(mn,w),mx=std::max(mx,w);
Init(n);
double l=mn,r=mx,mid;
for(int T=1; T<=31; ++T)
if(Check(n,mid=(l+r)*0.5)) l=mid;
else r=mid;
printf("%.3lf\n",l); return 0;
}

长链剖分 线段树1:

二分答案,然后判断是否存在一条长度在[L,R]的路径满足权值和非负。

考虑暴力DP,\(f[i][j]\)表示点\(i\)的子树中深度为\(j\)的路径权值的最大值。

显然可以长链剖分。因为每次匹配的长度是一个区间,所以可以用线段树维护。

复杂度\(O(n\log n\log v)\)。

然而线段树巨大的常数比用一堆vector的点分治还要慢。。但是我不会zkw。。

偏移重儿子\(f\)数组时\(f\)所有元素加的值(\(tag\))(甚至整个\(f\)数组)其实不需要。。

在线段树上的位置是对的,就直接用线段树上的节点值和\(dis\)就好了。

这是用\(f\)和\(tag\)的写法。

//27384kb	9288ms
#include <cstdio>
#include <cctype>
#include <algorithm>
//#define gc() getchar()
#define MAXIN 150000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
typedef long long LL;
const int N=1e5+5;
const double INF=1ll<<60; int n,L,R,Enum,H[N],nxt[N<<1],to[N<<1],len[N<<1],mxd[N],son[N],w[N],pos[N];
double X,f[N],tag[N];
char IN[MAXIN],*SS=IN,*TT=IN;
struct Segment_Tree
{
#define ls rt<<1
#define rs rt<<1|1
#define lson l,m,ls
#define rson m+1,r,rs
#define S N<<2
int cnt,A[S],id[N];
double mx[S];
#undef S
void Build(int l,int r,int rt)
{
A[++cnt]=rt;
if(l==r) {id[l]=rt; return;}
int m=l+r>>1; Build(lson), Build(rson);
}
void pre(int n)
{
Build(1,n,1);
}
inline void Init()
{
for(int i=1; i<=cnt; ++i) mx[A[i]]=-INF;
}
void Modify(int l,int r,int rt,int p,double v)
{
mx[rt]=std::max(mx[rt],v);
if(l==r) return;
int m=l+r>>1;
p<=m?Modify(lson,p,v):Modify(rson,p,v);
// Update(rt);
}
double Query(int l,int r,int rt,int L,int R)
{
if(L<=l && r<=R) return mx[rt];
int m=l+r>>1;
if(L<=m)
if(m<R) return std::max(Query(lson,L,R),Query(rson,L,R));
else return Query(lson,L,R);
return Query(rson,L,R);
}
}T; inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now;
}
inline void AE(int u,int v,int w)
{
to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum, len[Enum]=w;
to[++Enum]=u, nxt[Enum]=H[v], H[v]=Enum, len[Enum]=w;
}
void DFS1(int x,int fa)
{
int mx=-1;
for(int i=H[x],v; i; i=nxt[i])
if((v=to[i])!=fa) DFS1(v,x), mxd[v]>mx&&(mx=mxd[v],son[x]=v,w[x]=len[i]);
mxd[x]=++mx;
}
void DFS2(int x,int fa)
{
static int Index=0;
pos[x]=++Index;
if(son[x])
{
DFS2(son[x],x);
for(int i=H[x],v; i; i=nxt[i])
if((v=to[i])!=fa&&v!=son[x]) DFS2(v,x);
}
}
bool Solve(int x,int fa)
{
int px=pos[x]; f[px]=tag[px]=0;
if(!son[x]) {T.Modify(1,n,1,px,0); return 0;}
if(Solve(son[x],x)) return 1;
tag[px]+=tag[px+1]+w[x]-X, f[px]=-tag[px];//f[x][0]=0
if(mxd[x]>=L && tag[px]+T.Query(1,n,1,px+L,px+std::min(mxd[x],R))>=0) return 1;
T.Modify(1,n,1,px,f[px]);
for(int i=H[x],v; i; i=nxt[i])
if((v=to[i])!=fa && v!=son[x])
{
if(Solve(v,x)) return 1;
int pv=pos[v]; double val=tag[px]+tag[pv]+len[i]-X;
for(int j=0; j<=mxd[v]; ++j)
{
int l=std::max(0,L-j-1),r=std::min(mxd[x],R-j-1);
if(l<=r && val+f[pv+j]+T.Query(1,n,1,px+l,px+r)>=0) return 1;
}
val=tag[pv]+len[i]-X;
for(int j=0; j<=mxd[v]; ++j)
if(f[px+j+1]+tag[px]<f[pv+j]+val)
T.Modify(1,n,1,px+j+1,f[px+j+1]=f[pv+j]+val-tag[px]);
}
return 0;
}
bool Check(double mid)
{
T.Init(), X=mid; return Solve(1,1);
} int main()
{
n=read(),L=read(),R=read(); int mn=1e6,mx=0;
for(int i=1,u,v,w; i<n; ++i) u=read(),v=read(),w=read(),AE(u,v,w),mn=std::min(mn,w),mx=std::max(mx,w);
DFS1(1,1), DFS2(1,1), T.pre(n);
double l=mn,r=mx,mid;
for(int T=1; T<=31; ++T)
if(Check(mid=(l+r)*0.5)) l=mid;
else r=mid;
printf("%.3lf\n",l); return 0;
}

长链剖分 线段树2:

偏移重儿子\(f\)数组时\(f\)所有元素加的值(\(tag\))(甚至整个\(f\)数组)其实不需要。。

在线段树上的位置是对的,就直接用线段树上的节点值和\(dis\)就好了。

这就是后者的写法。

//25192kb	9500ms
#include <cstdio>
#include <cctype>
#include <algorithm>
//#define gc() getchar()
#define MAXIN 150000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
typedef long long LL;
const int N=1e5+5;
const double INF=1ll<<60; int n,L,R,Enum,H[N],nxt[N<<1],to[N<<1],len[N<<1],mxd[N],son[N],w[N],pos[N];
double X;
char IN[MAXIN],*SS=IN,*TT=IN;
struct Segment_Tree
{
#define ls rt<<1
#define rs rt<<1|1
#define lson l,m,ls
#define rson m+1,r,rs
#define S N<<2
int cnt,A[S],id[N];
double mx[S];
#undef S
void Build(int l,int r,int rt)
{
A[++cnt]=rt;
if(l==r) {id[l]=rt; return;}
int m=l+r>>1; Build(lson), Build(rson);
}
void pre(int n)
{
Build(1,n,1);
}
inline void Init()
{
for(int i=1; i<=cnt; ++i) mx[A[i]]=-INF;
}
void Modify(int l,int r,int rt,int p,double v)
{
mx[rt]=std::max(mx[rt],v);
if(l==r) return;
int m=l+r>>1;
p<=m?Modify(lson,p,v):Modify(rson,p,v);
// Update(rt);
}
double Query(int l,int r,int rt,int L,int R)
{
if(L<=l && r<=R) return mx[rt];
int m=l+r>>1;
if(L<=m)
if(m<R) return std::max(Query(lson,L,R),Query(rson,L,R));
else return Query(lson,L,R);
return Query(rson,L,R);
}
}T; inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now;
}
inline void AE(int u,int v,int w)
{
to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum, len[Enum]=w;
to[++Enum]=u, nxt[Enum]=H[v], H[v]=Enum, len[Enum]=w;
}
void DFS1(int x,int fa)
{
int mx=-1;
for(int i=H[x],v; i; i=nxt[i])
if((v=to[i])!=fa) DFS1(v,x), mxd[v]>mx&&(mx=mxd[v],son[x]=v,w[x]=len[i]);
mxd[x]=++mx;
}
void DFS2(int x,int fa)
{
static int Index=0;
pos[x]=++Index;
if(son[x])
{
DFS2(son[x],x);
for(int i=H[x],v; i; i=nxt[i])
if((v=to[i])!=fa&&v!=son[x]) DFS2(v,x);
}
}
bool Solve(int x,int fa,double dis)
{
static double tmp[N];
int px=pos[x]; T.Modify(1,n,1,px,dis);
if(!son[x]) return 0;
if(Solve(son[x],x,dis+w[x]-X)) return 1;
if(mxd[x]>=L && T.Query(1,n,1,px+L,px+std::min(mxd[x],R))-dis>=0) return 1;
for(int i=H[x],v; i; i=nxt[i])
if((v=to[i])!=fa && v!=son[x])
{
if(Solve(v,x,dis+len[i]-X)) return 1;
int pv=pos[v];
for(int j=0; j<=mxd[v]; ++j)
{
tmp[j]=T.mx[T.id[pv+j]];//另一种写法,这不能是f[pos[v]+j]啊,这样f是dis不是dp数组啊。
int l=std::max(0,L-j-1),r=std::min(mxd[x],R-j-1);
if(l<=r && tmp[j]+T.Query(1,n,1,px+l,px+r)-dis*2>=0) return 1;
}
for(int j=0; j<=mxd[v]; ++j) T.Modify(1,n,1,px+j+1,tmp[j]);
}
return 0;
}
bool Check(double mid)
{
T.Init(), X=mid; return Solve(1,1,0);
} int main()
{
n=read(),L=read(),R=read(); int mn=1e6,mx=0;
for(int i=1,u,v,w; i<n; ++i) u=read(),v=read(),w=read(),AE(u,v,w),mn=std::min(mn,w),mx=std::max(mx,w);
DFS1(1,1), DFS2(1,1), T.pre(n);
double l=mn,r=mx,mid;
for(int T=1; T<=31; ++T)
if(Check(mid=(l+r)*0.5)) l=mid;
else r=mid;
printf("%.3lf\n",l); return 0;
}

BZOJ.1758.[WC2010]重建计划(分数规划 点分治 单调队列/长链剖分 线段树)的更多相关文章

  1. [WC2010]重建计划(分数规划+点分治+单调队列)

    题目大意:给定一棵树,求一条长度在L到R的一条路径,使得边权的平均值最大. 题解 树上路径最优化问题,不难想到点分治. 如果没有长度限制,我们可以套上01分数规划的模型,让所有边权减去mid,求一条路 ...

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

    [Wc2010]重建计划 Time Limit: 40 Sec  Memory Limit: 162 MBSubmit: 4345  Solved: 1054[Submit][Status][Disc ...

  3. 【BZOJ 1758】【WC 2010】重建计划 分数规划+点分治+单调队列

    一开始看到$\frac{\sum_{}}{\sum_{}}$就想到了01分数规划但最终还是看了题解 二分完后的点分治,只需要维护一个由之前处理过的子树得出的$tb数组$,然后根据遍历每个当前的子树上的 ...

  4. BZOJ 1758 / Luogu P4292 [WC2010]重建计划 (分数规划(二分/迭代) + 长链剖分/点分治)

    题意 自己看. 分析 求这个平均值的最大值就是分数规划,二分一下就变成了求一条长度在[L,R]内路径的权值和最大.有淀粉质的做法但是我没写,感觉常数会很大.这道题可以用长链剖分做. 先对树长链剖分. ...

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

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

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

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

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

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

  8. 洛谷 P4292 - [WC2010]重建计划(长链剖分+线段树)

    题面传送门 我!竟!然!独!立!A!C!了!这!道!题!incredible! 首先看到这类最大化某个分式的题目,可以套路地想到分数规划,考虑二分答案 \(mid\) 并检验是否存在合法的 \(S\) ...

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

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

随机推荐

  1. 百度AI—人脸在线比对

    首先访问百度AI网站:https://cloud.baidu.com/,按照下图的指示点开到应用管理的页面. 穿件完成之后到管理中可以查看到对应的 添加工具类: using System; using ...

  2. java注解优缺点

    优点: 1.节省配置,减少配置文件大小 2.编译时即可查看正确与否,提高效率 缺点: 1.增加了程序的耦合性,因为注解保存在class文件中,而且比较分散 2.若要对配置进行修改需要重新编译 @aut ...

  3. 转载:JAVA序列化与反序列化 (作者:YSOcean)

    原文:https://www.cnblogs.com/ysocean/p/6870069.html File 类的介绍:http://www.cnblogs.com/ysocean/p/6851878 ...

  4. Android数据存储:SDCard

    Android数据存储之SDCard 0.获取sd卡路径. 1.讲述 Environment 类. 2.讲述 StatFs 类. 3.完整例子读取 SDCard 内存 0.获取sd卡路径 方法一: p ...

  5. CopyPropertis

    commons-beanutils.jar PropertyUtils.copyProperties(Object dest, Object orig) spring-beans.jar BeanUt ...

  6. annoy ANN算法 调参

    search_k serach_k越大,越准确,但是要在时间和准确率之间取个trade off During the query it will inspect up to search_k node ...

  7. 20个实用的webApp前端开发技巧

    自Iphone和Android这两个牛逼的手机操作系统发布以来,在互联网界从此就多了一个新的名词-WebApp(意为基于WEB形式的应用程序,运行在高端的移动终端设备). 开发者们都知道在高端智能手机 ...

  8. echarts饼图不显示数据为0的数据

    首先阐述下为什么会有这个需求,这个和echarts自身的显示效果有关. 如果你选择的展示图形为饼图,然后你的数据里有一条数据为0,那么展示的数据就为一条直线,看上去效果并不好, 会很突兀. 当然如果你 ...

  9. 【mysql】MySQLdb返回字典方法

    来源:http://blog.csdn.net/zgl_dm/article/details/8710371 默认mysqldb返回的是元组,这样对使用者不太友好,也不利于维护下面是解决方法 impo ...

  10. MySQL 由 5.7 升级为 8.0 之后,Laravel 的配置改动

    开发机上升级了 MySQL 8.0, 原有的 Laravel 5.5 项目就启动失败了. 报错信息是: [2018-05-30 11:17:37] local.ERROR: SQLSTATE[4200 ...