浅谈树分治:https://www.cnblogs.com/AKMer/p/10014803.html

题目传送门:https://www.lydsy.com/JudgeOnline/problem.php?id=1758

先来讲讲部分分吧。

对于\(20\)%的数据

我们可以对于每个点为根\(dfs\)一遍,然后用\(deep\)在\([L,R]\)之间的点与当前根的距离除以深度来更新答案;

时间复杂度:\(O(n^2)\)

空间复杂度:\(O(n)\)

对于另外\(30\)%的数据

因为是一条链,我们可以把\(n-1\)条边拆出来放到一个数组里,就相当于找一个长度在\([L,R]\)之间的子段平均值最大。一看到平均值我们就可以想到二分。我们二分一个\(limit\),然后把每条边都减去\(limit\),这个时候问题就转化成了怎么去检查这个数组有没有子段长度在\([L,R]\)内,并且子段和大于\(0\)。子段和我们可以用前缀和相减来表示,用单调上升队列维护一下即可。队头就是在所有与当前点距离为\([L,R]\)之内的点,前缀和最小的那一个。用当前点的前缀和减去队头点的前缀和判断是否大于等于\(0\)即可。

时间复杂度:\(O(nlogans)\)

空间复杂度:\(O(n)\)

部分分代码如下:

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll; const int maxn=1e5+5;
const double eps=1e-6; bool line;
double ans;
double sum[maxn];
int n,L,R,tot,rt;
int a[maxn],deg[maxn];
int now[maxn],pre[maxn*2],son[maxn*2],val[maxn*2]; int read() {
int x=0,f=1;char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
return x*f;
} void add(int a,int b,int c) {
pre[++tot]=now[a];
now[a]=tot,son[tot]=b,val[tot]=c;
} void dfs(int fa,int u,int dep,ll dis) {
if(dep>R)return;
if(L<=dep&&dep<=R)ans=max(ans,1.0*dis/dep);
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(v!=fa)dfs(u,v,dep+1,dis+val[p]);
} void solve1() {
for(int i=1;i<=n;i++)
dfs(0,i,0,0);
printf("%.3lf\n",ans);
} void make_a(int fa,int u) {
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(v!=fa)a[++n]=val[p],make_a(u,v);
} struct data {
int pos;
double v; data() {} data(int _pos,double _v) {
pos=_pos,v=_v;
}
}list[maxn]; bool check(double limit) {
int h=0,t=0;
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+1.0*a[i]-limit;
for(int i=L;i<=n;i++) {
while(h!=t&&i-list[h].pos>R)h++;
while(t!=h&&list[t-1].v>sum[i-L])t--;
list[t++]=data(i-L,sum[i-L]);
if(sum[i]-list[h].v>0)return 1;
}
return 0;
} void solve2() {
n=0;make_a(0,rt);
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+a[i];
double l=0,r=sum[n];
while(l<r-eps) {
double mid=(l+r)/2;
if(check(mid))l=mid;
else r=mid;
}
printf("%.3lf\n",l);
} int main() {
n=read(),L=read(),R=read();
for(int i=1;i<n;i++) {
int a=read(),b=read(),c=read();
add(a,b,c),add(b,a,c);deg[a]++,deg[b]++;
}
for(int i=1;i<=n;i++) {
if(deg[i]>2)line=1;
if(deg[i]==1)rt=i;
}
if(n<=5000)solve1();//20%
else if(!line)solve2();//另外30%
return 0;
}

部分分已经提示了正解了。对于这题,我们可以二分一个答案,然后让每条边减去这个值,判断树里面有没有一条长度在\([L,R]\)之内的路径权值和大于等于\(0\)。同样的,我们也可以用单调队列维护。

一开始我觉得不好做,因为我\(dfs\)子树去更新答案的话还要还原单调队列。然后就傻傻地开栈记录被弹出队列的东西……假设当前点的子树做完之后再把被当前点弹出去的东西塞回去……然后\(TLE\)。

后来看了\(hzw\)的做法,把\(dfs\)改成\(bfs\),深度就会递增了,就不需要还原单调队列了。洛谷可以过,但是\(BZOJ\)过不了。没办法了,只能自己卡常了。

我们设\(f_i\)表示在已经遍历过的子树中长度为\(i\)的路径最大值是多少,\(g_i\)是当前要询问的子树内长度为\(i\)的路径最大值是多少,然后就直接用\(f\)和\(g\)两个数组跑单调队列就行了。另外还可以用到SPOJ1825 Free Tour II里的优化,把子树遍历顺序按照最深的深度从小到大排序,在\(BZOJ\)就可以过了。

\(BZOJ\)神仙还是多,\(rk1\)是打表的我们不管他,\(rk2\)居然是个边分治……根本看不懂他代码……

点分治版代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll; const int maxn=1e5+5;
const double eps=1e-5; bool vis[maxn];
int n,L,R,N,tot,mx,rt;
int siz[maxn],Q[maxn],V[maxn],depest[maxn];
double limit,ans,Mx,f[maxn],g[maxn],dis[maxn];
int now[maxn],pre[maxn*2],son[maxn*2],val[maxn*2]; int read() {
int x=0,f=1;char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
return x*f;
} void add(int a,int b,int c) {
pre[++tot]=now[a];
now[a]=tot,son[tot]=b,val[tot]=c;
} bool cmp(int a,int b) {
return depest[a]<depest[b];
} void find_rt(int fa,int u) {
int res=0;siz[u]=1;
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[v]&&v!=fa)find_rt(u,v),siz[u]+=siz[v],res=max(res,siz[v]);
res=max(res,N-siz[u]);
if(res<mx)mx=res,rt=u;
} void dfs(int fa,int u,int dep,ll len) {
siz[u]=1,dis[u]=len,mx=max(mx,dep);
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[v]&&v!=fa)dfs(u,v,dep+1,len+val[p]),siz[u]+=siz[v];
} void make_g(int fa,int u,int dep) {
g[dep]=max(g[dep],dis[u]);
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[v]&&v!=fa)make_g(u,v,dep+1);
} bool query(int a,int b) {
for(int i=1;i<=b;i++)f[i]-=limit*i,g[i]-=limit*i;
int h=0,t=0,pos=a;bool res=0;
for(int i=1;i<=b;i++) {
while(i+pos>=L&&pos>=0) {
while(h!=t&&f[Q[t-1]]<f[pos])t--;
Q[t++]=pos;pos--;
}
while(h!=t&&Q[h]+i>R)h++;
if(h!=t&&f[Q[h]]+g[i]>=0)res=1;
}
for(int i=1;i<=b;i++)f[i]+=limit*i,g[i]+=limit*i;
return res;
} bool check(int u) {
for(int i=1;i<=depest[V[tot]];i++)
f[i]=g[i]=-1e15;
for(int i=1;i<=tot;i++) {
make_g(u,V[i],1);
if(query(depest[V[i-1]],depest[V[i]]))return 1;
for(int j=1;j<=depest[V[i]];j++)
f[j]=max(f[j],g[j]),g[j]=-1e15;
}
return 0;
} void work(int u,int size) {
N=size,rt=mx=n+1,find_rt(0,u);
u=rt,vis[u]=1,tot=0;
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[v]) {
mx=0,dfs(u,v,1,val[p]);
depest[v]=mx,V[++tot]=v;
}
sort(V+1,V+tot+1,cmp);
double l=ans,r=Mx;
while(l<r-eps) {
limit=(l+r)/2;
if(check(u))l=limit;
else r=limit;
}ans=l;
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[v])work(v,siz[v]);
} int main() {
n=read(),L=read(),R=read();
for(int i=1;i<n;i++) {
int a=read(),b=read(),c=read();
add(a,b,c),add(b,a,c);Mx=max(Mx,1.0*c);
}work(1,n);printf("%.3lf\n",ans);
return 0;
}

边分治版代码如下:

#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int,int> pii;
#define fr first
#define sc second const int maxn=2e5+5;
const double eps=1e-5; bool vis[maxn];
int n,tot,cnt,mx,id,L,R,N;
int siz[maxn],Q[maxn],dep[maxn],depest[maxn];
double ans,limit,Mx,f[maxn],g[maxn],dis[maxn];
int now[maxn],pre[maxn*2],son[maxn*2],val[maxn*2]; vector<pii>to[maxn];
vector<pii>::iterator it; inline int read() {
int x=0,f=1;char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
return x*f;
} inline void add(int a,int b,int c) {
pre[++tot]=now[a];
now[a]=tot,son[tot]=b,val[tot]=c;
} inline void find_son(int fa,int u) {
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(v!=fa)to[u].push_back(make_pair(v,val[p])),find_son(u,v);
} inline void rebuild() {
tot=1;memset(now,0,sizeof(now));
for(int i=1;i<=cnt;i++) {
int size=to[i].size();
if(size<=2) {
for(it=to[i].begin();it!=to[i].end();it++) {
pii tmp=*it;
add(i,tmp.fr,tmp.sc),add(tmp.fr,i,tmp.sc);
}
}
else {
pii u1=make_pair(++cnt,0),u2;
if(size==3)u2=to[i].front();
else u2=make_pair(++cnt,0);
add(i,u1.fr,u1.sc),add(u1.fr,i,u1.sc);
add(i,u2.fr,u2.sc),add(u2.fr,i,u2.sc);
if(size==3) {
for(int j=1;j<=2;j++)
to[cnt].push_back(to[i].back()),to[i].pop_back();
}
else {
int p=0;
for(it=to[i].begin();it!=to[i].end();it++) {
if(!p)to[cnt-1].push_back(*it);
else to[cnt].push_back(*it);p^=1;
}
}
}
}
} inline void find_edge(int fa,int u) {
siz[u]=1;
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[p>>1]&&v!=fa) {
find_edge(u,v),siz[u]+=siz[v];
if(abs(N-2*siz[v])<mx)
mx=abs(N-2*siz[v]),id=p>>1;
}
} inline void dfs(int fa,int u,int Dep,double len) {
siz[u]=1,dis[u]=len,dep[u]=Dep,mx=max(mx,Dep);
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[p>>1]&&v!=fa)dfs(u,v,Dep+(val[p]!=0),len+val[p]),siz[u]+=siz[v];
} inline void make(double *a,int fa,int u) {
a[dep[u]]=max(a[dep[u]],dis[u]);
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[p>>1]&&v!=fa)make(a,u,v);
} inline bool query(int a,int b) {
for(int i=1;i<=a;i++)f[i]-=limit*i;
for(int i=1;i<=b;i++)g[i]-=limit*i;
int h=0,t=0,pos=a;bool res=0;
for(int i=1;!res&&i<=b;i++) {
while(i+pos+(val[id<<1]>0)>=L&&pos>=0) {
while(h!=t&&f[Q[t-1]]<f[pos])t--;
Q[t++]=pos,pos--;
}
while(h!=t&&i+Q[h]+(val[id<<1]>0)>R)h++;
if(h!=t&&f[Q[h]]+g[i]+val[id<<1]-(val[id<<1]>0)*limit>=0)res=1;
}
for(int i=1;i<=a;i++)f[i]+=limit*i;
for(int i=1;i<=b;i++)g[i]+=limit*i;
return res;
} inline bool check() {
int u1=son[id<<1],u2=son[id<<1|1];
if(depest[u1]>depest[u2])swap(u1,u2);
for(int i=1;i<=depest[u2];i++)f[i]=g[i]=-1e15;
make(f,0,u1),make(g,0,u2);
return query(depest[u1],depest[u2]);
} inline void work(int u,int size) {
if(size<2)return;
N=size,mx=id=cnt+1,find_edge(0,u),vis[id]=1;
int u1=son[id<<1],u2=son[id<<1|1];
mx=0,dfs(0,u1,0,0),depest[u1]=mx;
mx=0,dfs(0,u2,0,0),depest[u2]=mx;
double l=ans,r=Mx;
while(l<r-eps) {
limit=(l+r)/2;
if(check())l=limit;
else r=limit;
}ans=l;
work(u1,siz[u1]),work(u2,siz[u2]);
} int main() {
cnt=n=read(),L=read(),R=read();
for(int i=1;i<n;i++) {
int a=read(),b=read(),c=read();
add(a,b,c),add(b,a,c);Mx=max(Mx,1.0*c);
}find_son(0,1),rebuild();
work(1,cnt);printf("%.3lf\n",ans);
return 0;
}

BZOJ1758:[WC2010]重建计划的更多相关文章

  1. BZOJ1758: [Wc2010]重建计划

    题解: 这题我居然做了一星期?... 平均值的极值其实也可以算是一种分数规划,只不过分母上b[i]=1 然后我们就可以二分这个值.类似与 HNOI最小圈 如果没有 链的长度的限制的话,我们直接两遍df ...

  2. bzoj1758 [Wc2010]重建计划 & bzoj2599 [IOI2011]Race

    两题都是树分治. 1758这题可以二分答案avgvalue,因为avgvalue=Σv(e)/s,因此二分后只需要判断Σv(e)-s*avgvalue是否大于等于0,若大于等于0则调整二分下界,否则调 ...

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

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

  4. [BZOJ1758][WC2010]重建计划(点分治+单调队列)

    点分治,对于每个分治中心,考虑求出经过它的符合长度条件的链的最大权值和. 从分治中心dfs下去取出所有链,为了防止两条链属于同一个子树,我们一个子树一个子树地处理. 用s1[i]记录目前分治中心伸下去 ...

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

    题目:http://www.lydsy.com/JudgeOnline/problem.php?id=1758 01分数规划,所以我们对每个重心进行二分.于是问题转化为Σw[e]-mid>=0, ...

  6. BZOJ1758 WC2010 重建计划 二分答案、点分治、单调队列

    传送门 看到平均数最大,自然地想到二分答案.那么我们的$check$函数就是要求:是否存在一条长度在$[L,U]$的路径,满足其权值和$\geq 0$. 看到长度在$[L,U]$,自然地想到点分治求解 ...

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

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

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

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

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

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

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

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

随机推荐

  1. Java中的线程池ExecutorService

    示例 import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.u ...

  2. POJ 1787 Charlie&#39;s Change

    多重背包 可行性+路径记录 题意是说你要用很多其它的零钱去买咖啡.最后输出你分别要用的 1,5 ,10 .25 的钱的数量. 多重背包二进制分解.然后记录下 这个状态.最后逆向推就可以. #inclu ...

  3. ASP.NET动态网站制作(9)-- JQ(1)

    前言:从这节课开始讲jQuery的相关内容,这节课主要围绕jQuery的选择器展开. 内容: 1.jQuery是一个优秀的js框架,目前企业里大多数都是用jQuery(以下简称jq).jq是对js里一 ...

  4. fzu 2039 Pets (简单二分图 + (最大流 || 二分图))

    Are you interested in pets? There is a very famous pets shop in the center of the ACM city. There ar ...

  5. struts2_6_多个struts配置文件的应用

    在大部分应用里,随着应用规模的添加,系统中Action的数量也会大量添加.导致struts.xml配置文件变的很臃肿,为了避免struts.xml文件过于庞大.臃肿,提高struts.xml文件的可读 ...

  6. Times[2017-01-25at JiNan]

    Times[问题描述 ]小 y 作为一名资深的 dotaer,对视野的控制有着深刻的研究.每个单位在一段特定的时间内会出现在小 y 的视野内,除此之外的时间都在小 y 看不到的地方.在小 y 看来,视 ...

  7. <转载> pycharm快捷键及一些常用设置

    1.编辑(Editing ) Ctrl + Space 基本的代码完成(类.方法.属性)Ctrl + Alt + Space 快速导入任意类Ctrl + Shift + Enter 语句完成Ctrl ...

  8. 【BZOJ4408】[Fjoi 2016]神秘数 主席树神题

    [BZOJ4408][Fjoi 2016]神秘数 Description 一个可重复数字集合S的神秘数定义为最小的不能被S的子集的和表示的正整数.例如S={1,1,1,4,13},1 = 12 = 1 ...

  9. JAVA数据类型(转)

     java中数据的基本类型分为: 基本数据类型和引用数据类型,对此不多介绍:   接下来讨论一下java中数据类型存储在哪     基本数据类型存储在哪,取决于基本类型在哪声明:          1 ...

  10. plus.os.name 无法正确执行的问题

    使用HTML5+开发App的时候, 如果碰到正确的代码却无法出现预期的执行效果, 请检查模块权限配置是否OK? 比如plus.os.name, 需要Device权限 ;