BZOJ1758[Wc2010]重建计划——分数规划+长链剖分+线段树+二分答案+树形DP
题目描述
输入
第一行包含一个正整数N,表示X国的城市个数. 第二行包含两个正整数L和U,表示政策要求的第一期重建方案中修建道路数的上下限 接下来的N-1行描述重建小组的原有方案,每行三个正整数Ai,Bi,Vi分别表示道路(Ai,Bi),其价值为Vi 其中城市由1..N进行标号
输出
输出最大平均估值,保留三位小数
样例输入
2 3
1 2 1
1 3 2
1 4 3
样例输出
提示
N<=100000,1<=L<=U<=N-1,Vi<=1000000
这题算是长链剖分最经典的一道题了。
首先要找一个最优方案,直接DP显然不好做,那么考虑二分答案然后验证,因为是浮点数,要注意精度问题。
假设当前二分的答案是k,判断答案是否满足时原式也就转化成了$\frac{\sum_{ }^{ }vi}{|S|}>=k$,将分母移到不等式右边得到$\sum_{ }^{ }vi>=k*|S|$
将右边的部分移到左边就变成了$\sum_{ }^{ }vi-k*|S|>=0$
因为vi的个数就是|S|,因此将k*|S|放到Σ里面,判断就变成了$\sum_{ }^{ }(vi-k)>=0$
每次判断只要把每条边的边权和减k,再判断能否有一条路径边权和大于等于0就好了。
怎么判断呢?很容易想到O(n^2)dp,设f[i][j]表示i子树中与i距离为j的链的边权和最大值,枚举另一棵子树找到链长在[L-j,R-j]之内的边权最大值。
O(n^2)dp显然不行,但观察到dp是可合并的以深度为下标的转移方程,因此可以用长链剖分优化成O(n)。
怎么优化呢?
首先对整棵树长链剖分求出树剖序,再把树剖序架到线段树上,因为整棵树是由所有长链组成的,每条长链因为是优先遍历所以在树剖序上是连续的一段。
也就是说树剖序上的每一段都是树剖出的一条长链。那么每个点子树中每个深度的信息就可以都存到这个点往下的长链上。
当做树形DP时,每个点对于重儿子回溯时不做任何操作,直接继承;当轻儿子回溯时枚举轻儿子每个深度的边权和最大值,在长链上找到对应区间求最大值来更新答案。
然后再把这个轻儿子的信息合并到长链上。因为长链上存的是之前所有遍历过的子树合并后的信息,所以相当于每个点子树中有用的信息都在这个点往下的长链上。最后别忘了考虑从上到下的每条直链。
这样DP是O(n)的,再加上线段树的O(log)和二分的O(log)一共是O(nlog2n)。
至于这样DP为什么是O(n)的?
因为每个点对重儿子是直接继承的,而每个点需要被DP当且仅当它是轻儿子时,这时它一定是一个长链的链头,DP的时间复杂度是这个点往下的长链长度,那么DP的总复杂度就是每条长链的链长总和,也就是O(n)。
更加详细的有关长链剖分的讲解参见->长链剖分
#include<set> #include<map> #include<cmath> #include<queue> #include<stack> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int n; int L,R; int tot; int cnt; int x,y,z; double ans; int s[100010]; int d[100010]; int f[100010]; int v[200010]; int mn[100010]; int to[200010]; int son[100010]; int num[100010]; int head[100010]; int next[200010]; double mx[800010]; double val[200010]; double dis[100010]; double dep[100010]; double INF=1e15; double eps=1e-4; void add(int x,int y,int z) { tot++; next[tot]=head[x]; head[x]=tot; to[tot]=y; v[tot]=z; } void dfs(int x) { d[x]=d[f[x]]+1; mn[x]=d[x]; for(int i=head[x];i;i=next[i]) { if(to[i]!=f[x]) { f[to[i]]=x; dfs(to[i]); mn[x]=max(mn[x],mn[to[i]]); if(mn[to[i]]>mn[son[x]]) { son[x]=to[i]; } } } } void dfs2(int x) { s[x]=++cnt; if(son[x]) { dfs2(son[x]); } for(int i=head[x];i;i=next[i]) { if(to[i]!=f[x]&&to[i]!=son[x]) { dfs2(to[i]); } } } void build(int rt,int l,int r) { mx[rt]=-INF; if(l==r) { num[l]=rt; return ; } int mid=(l+r)>>1; build(rt<<1,l,mid); build(rt<<1|1,mid+1,r); } void change(int rt,int l,int r,int k,double x) { if(l==r) { mx[rt]=max(mx[rt],x); return ; } int mid=(l+r)>>1; if(k<=mid) { change(rt<<1,l,mid,k,x); } else { change(rt<<1|1,mid+1,r,k,x); } mx[rt]=max(mx[rt<<1],mx[rt<<1|1]); } double query(int rt,int l,int r,int L,int R) { if(L>R) { return -INF; } if(L<=l&&r<=R) { return mx[rt]; } int mid=(l+r)>>1; double res=-INF; if(L<=mid) { res=max(res,query(rt<<1,l,mid,L,R)); } if(R>mid) { res=max(res,query(rt<<1|1,mid+1,r,L,R)); } return res; } void tree_dp(int x) { change(1,1,n,s[x],dis[x]); for(int i=head[x];i;i=next[i]) { if(son[x]==to[i]) { dis[son[x]]=dis[x]+val[i]; tree_dp(son[x]); } } for(int i=head[x];i;i=next[i]) { if(to[i]!=f[x]&&to[i]!=son[x]) { dis[to[i]]=dis[x]+val[i]; tree_dp(to[i]); for(int j=1;j<=mn[to[i]]-d[x];j++) { dep[j]=mx[num[s[to[i]]+j-1]]; if(j<=R) { ans=max(ans,query(1,1,n,max(s[x],s[x]+L-j),min(s[x]+mn[x]-d[x],s[x]+R-j))+dep[j]-2*dis[x]); } } for(int j=1;j<=mn[to[i]]-d[x];j++) { change(1,1,n,s[x]+j,dep[j]); } } } ans=max(ans,query(1,1,n,s[x]+L,min(s[x]+mn[x]-d[x],s[x]+R))-dis[x]); } int main() { scanf("%d%d%d",&n,&L,&R); for(int i=1;i<n;i++) { scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z); } dfs(1); dfs2(1); double l=0; double r=1000000; while(r-l>eps) { double mid=(l+r)/2; for(int i=1;i<=tot;i++) { val[i]=v[i]-mid; } ans=-INF; dis[1]=0; build(1,1,n); tree_dp(1); if(ans<0) { r=mid; } else { l=mid; } } printf("%.3lf",l); }