[Luogu P4292] [BZOJ 1758] [WC2010]重建计划

BZOJ 传送门
洛谷传送门

题目描述

X国遭受了地震的重创, 导致全国的交通近乎瘫痪,重建家园的计划迫在眉睫。X国由 N N 个城市组成, 重建小组提出,仅需建立N1条道路即可使得任意两个城市互相可达。于是,重建小组很快提出了一个包含 N1 N − 1 条道路的方案,并满足城市之间两两可达,他们还计算评估了每条道路 e e 建设之后可以带来的价值v(e)
由于重建计划复杂而艰难,经费也有一定限制。因此,政府要求第一期重建工程修建的道路数目为 k k 条,但需满足LkU, 即不应少于 L L 条,但不超过U条。同时,为了最大化利用率,要求建设的这些道路恰好组成一条简单路径,即所建设的 k k 条路径可以构成一个排列e1=(p1,q1),e2=(p2,q2),...ek=(pk,qk), 对于 1i<k 1 ≤ i < k , 有( qi=pi+1 q i = p i + 1 )。

重建小组打算修改他们的原有方案以满足要求,即在原有的 N1 N − 1 条道路中寻找一条路径 S S 作为新的方案,使得新方案中的道路平均价值

AvgValue=eSv(e)|S|

最大。这里 v(e) v ( e ) 表示道路 e e 的价值,|S|表示新方案中道路的条数。请你帮助重建小组寻找一个最优方案。 注: 在本题中 L L U的设置将保证有解。

输入输出格式

输入格式:

第一行包含一个正整数 N N ,表示X国的城市个数。
第二行包含两个正整数L U U ,表示政府要求的第一期重建方案中修建道路数的上下限。
接下来的N1行描述重建小组的原有方案,每行三个正整数 ai,bi,vi a i , b i , v i ,分别表示道路 (ai,bi) ( a i , b i ) ,其价值为 vi v i 。其中城市由 1N 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 % 的数据, N5000 N ≤ 5 000 ;
另有 30% 30 % 的数据, N100000 N ≤ 100 000 , 原有方案恰好为一条路径(链);
对于 100% 100 % 的数据, N100000,1LUN1,vi106 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 的点的边权前缀和最大值。 对于每一个在子树上的点D,设其深度为 dep d e p , 则在线段树上对应一段区间为 [Ldep,Udep] [ 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 &cent, 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);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值