BZOJ 传送门
洛谷传送门
题目描述
X国遭受了地震的重创, 导致全国的交通近乎瘫痪,重建家园的计划迫在眉睫。X国由
N
N
个城市组成, 重建小组提出,仅需建立条道路即可使得任意两个城市互相可达。于是,重建小组很快提出了一个包含
N−1
N
−
1
条道路的方案,并满足城市之间两两可达,他们还计算评估了每条道路
e
e
建设之后可以带来的价值。
由于重建计划复杂而艰难,经费也有一定限制。因此,政府要求第一期重建工程修建的道路数目为
k
k
条,但需满足, 即不应少于
L
L
条,但不超过条。同时,为了最大化利用率,要求建设的这些道路恰好组成一条简单路径,即所建设的
k
k
条路径可以构成一个排列, 对于
1≤i<k
1
≤
i
<
k
, 有(
qi=pi+1
q
i
=
p
i
+
1
)。
重建小组打算修改他们的原有方案以满足要求,即在原有的
N−1
N
−
1
条道路中寻找一条路径
S
S
作为新的方案,使得新方案中的道路平均价值
最大。这里 v(e) v ( e ) 表示道路 e e 的价值,表示新方案中道路的条数。请你帮助重建小组寻找一个最优方案。 注: 在本题中 L L 和的设置将保证有解。
输入输出格式
输入格式:
第一行包含一个正整数
N
N
,表示X国的城市个数。
第二行包含两个正整数、
U
U
,表示政府要求的第一期重建方案中修建道路数的上下限。
接下来的行描述重建小组的原有方案,每行三个正整数
ai,bi,vi
a
i
,
b
i
,
v
i
,分别表示道路
(ai,bi)
(
a
i
,
b
i
)
,其价值为
vi
v
i
。其中城市由
1…N
1
…
N
标号。
输出格式:
仅包含一行,为一个实数
AvgValue
A
v
g
V
a
l
u
e
,即最大平均价值。
小数点后保留三位。
输入输出样例
输入样例#1:
4
2 3
1 2 1
1 3 2
1 4 3
输出样例#1:
2.500
说明
新方案中选择路径
(3,1)
(
3
,
1
)
,
(1,4)
(
1
,
4
)
可以得到的平均价值为
2.5
2.5
,为最大平均价值。
对于
20%
20
%
的数据,
N≤5000
N
≤
5
000
;
另有
30%
30
%
的数据,
N≤100000
N
≤
100
000
, 原有方案恰好为一条路径(链);
对于
100%
100
%
的数据,
N≤100000,1≤L≤U≤N−1,vi≤106
N
≤
100
000
,
1
≤
L
≤
U
≤
N
−
1
,
v
i
≤
10
6
。
解题分析
保留三位小数和4s的时限这些条件可以看出这道题是二分答案+点分树, 然后博主就不会做了…
搜了搜题解, 发现此题是一道分数规划问题。
最简单无脑不需二分的想法是直接做一遍点分, 同时对经过分治重心的边暴力更新。但这样搞是每一层
O(N2)
O
(
N
2
)
,总复杂度达到了
O(N2log2(N))
O
(
N
2
l
o
g
2
(
N
)
)
显然不可过。
在这道题中, 我们想要求得一条路径使得其平均值最大, 那么可以二分这个平均值
AveValve
A
v
e
V
a
l
v
e
, 将所有边的边权减去这个二分得到的值。如果一条路径长度位于
[L,U]
[
L
,
U
]
之间且边权和大于0, 则说明这个值是可行的。
现在我们有一个
O(Nlog3(N))
O
(
N
l
o
g
3
(
N
)
)
的做法。 对于每个分治重心, 我们考虑用线段树维护到分治重心距离为
x
x
的点的边权前缀和最大值。 对于每一个在子树上的点,设其深度为
dep
d
e
p
, 则在线段树上对应一段区间为
[L−dep,U−dep]
[
L
−
d
e
p
,
U
−
d
e
p
]
, 我们只需要查区间最大值即可。
但经网上大神们实验, 这个复杂度也是过不去的… 我们需要一个
O(N)
O
(
N
)
更新一层分治重心的算法。
上面我们说到了, 每一个子树上的点有唯一对应的一段查询区间, 并且这个区间随子树上点深度的增大而向更小方向移动。这样具有单调性的查询可以用单调队列来优化至
(O(N))
(
O
(
N
)
)
。
还有很多卡常的小细节, 其实做到了常数并不大, 详见代码。
#include <cstdio>
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <cmath>
#define R register
#define W while
#define gc getchar()
#define db double
#define IN inline
#define EPS 1e-4
#define INF 1e12
#define MX 200005
bool neg;
template <class T>
IN void in(T &x)
{
x = 0; R char c = gc;
W (!isdigit(c)) {if(c == '-') neg = true; c = gc;}
W (isdigit(c))
x = (x << 1) + (x << 3) + c - 48, c = gc;
if(neg) neg = false, x = -x;
}
db ans, len[MX], rec[MX];
int dot, D, U, cnt, deal, root, ub, dpmx, cot;
int dep[MX], fat[MX], siz[MX], mx[MX], que[MX],
sub[MX], ds[MX], q[MX], head[MX], far[MX];
bool vis[MX];
IN bool cmp(const int &x, const int &y)
{return far[x] < far[y];}
struct Edge
{
int to, len, nex;
}edge[MX << 1];
IN void addedge(const int &from, const int &to, const int &len)
{
edge[++cnt] = {to, len, head[from]};
head[from] = cnt;
}
namespace FP
{
IN bool check(const int ¢, const db &cut)
{
R int lb, rb, hd, tl, now, dl;
dpmx = 0;
for (R int t = 1; t <= cot; ++t)
{
if(vis[sub[t]]) continue;
que[hd = tl = 0] = sub[t];
dep[sub[t]] = 1;
len[sub[t]] = ds[sub[t]] - cut;
fat[sub[t]] = cent;
W (hd <= tl)
{
now = que[hd++];
for (R int i = head[now]; i; i = edge[i].nex)
//BFS一遍, 在队列中点深度不降
{
if(edge[i].to == fat[now] || vis[edge[i].to]) continue;
que[++tl] = edge[i].to, fat[edge[i].to] = now,
dep[edge[i].to] = dep[now] + 1, len[edge[i].to] = edge[i].len + len[now] - cut;
}
}
lb = 1, rb = 0, dl = dpmx;//dpmx指处理的所有子树中最深的深度
for (R int i = 0; i <= tl; ++i)
{
now = que[i];
W (dl >= 0 && dl + dep[now] >= D)//更新能够加入队列的最大值
{
W (lb <= rb && rec[q[rb]] <= rec[dl]) --rb;
q[++rb] = dl, --dl;
}
W (lb <= rb && dep[now] + q[lb] > U) ++lb;//处理超过上限的部分
if(lb <= rb && len[now] + rec[q[lb]] >= EPS) return true;
}
for (R int i = dpmx + 1; i <= dep[que[tl]]; ++i) rec[i] = -INF;//多出的部分赋为新值
for (R int i = 0; i <= tl; ++i) rec[dep[que[i]]] = std::max(rec[dep[que[i]]], len[que[i]]);//更新最优答案
dpmx = std::max(dpmx, dep[que[tl]]);
}
return false;
}
}
namespace Dtdv
{
void DFS(const int &now, const int &fa, const int &fr)
{//far 存子树最大深度, 方便分数规划时对子树排序
siz[now] = 1; far[now] = fr;
for (R int i = head[now]; i; i = edge[i].nex)
{
if(vis[edge[i].to] || edge[i].to == fa) continue;
DFS(edge[i].to, now, fr + 1);
siz[now] += siz[edge[i].to];
far[now] = std::max(far[now], far[edge[i].to]);
}
}
void getroot(const int &now, const int &fa)
{
mx[now] = 0;
for (R int i = head[now]; i; i = edge[i].nex)
{
if(vis[edge[i].to] || edge[i].to == fa) continue;
getroot(edge[i].to, now);
mx[now] = std::max(mx[now], siz[edge[i].to]);
}
mx[now] = std::max(mx[now], deal - siz[now]);
if(mx[now] < mx[root]) root = now;
}
void solve(R int now)
{
DFS(now, 0, 0); cot = 0;
for (R int i = head[now]; i; i = edge[i].nex)
{
if(vis[edge[i].to]) continue;
sub[++cot] = edge[i].to, ds[edge[i].to] = edge[i].len;//ds存分治重心到子树上第一个节点的距离
}
std::sort(sub + 1, sub + 1 + cot, cmp);
db dn = ans, up = ub, mid;
W (233)//二分确定答案
{
if(up - dn < EPS) break;
mid = (up + dn) / 2.0;
if(FP::check(now, mid)) dn = mid;
else up = mid;
}
ans = dn; vis[now] = true;
for (R int i = head[now]; i; i = edge[i].nex)
{
if(vis[edge[i].to]) continue;
DFS(edge[i].to, 0, 0);
mx[root = 0] = deal = siz[edge[i].to];
if(siz[edge[i].to] < D) continue;
getroot(edge[i].to, 0);
solve(root);
}
}
}
int main(void)
{
int a, b, c;
in(dot), in(D), in(U);
for (R int i = 1; i < dot; ++i)
in(a), in(b), in(c), addedge(a, b, c), addedge(b, a, c), ub = std::max(ub, c);//提前确定二分上界, 减少二分次数
Dtdv::DFS(1, 0, 0);
mx[root = 0] = deal = siz[1];
Dtdv::getroot(1, 0);
Dtdv::solve(root);
printf("%.3lf", ans);
}