潜龙未见静水流,沉默深藏待时秋。一朝破空声势振,惊世骇俗展雄猷。
随笔 - 82, 文章 - 0, 评论 - 3, 阅读 - 2153

CF566C Logistical Questions 题解

目录

题目描述

给定一棵 n 个点的树,点有点权 wi ,边有边权 li ,两点间的距离为边权和的 32 次方。

求这棵树的带权重心 rt ,以及所有点到 rt 的带权距离和。

数据范围

  • 1n2105
  • 0wi108,1li1000

相对或绝对误差不得超过 106

时间限制 2s ,空间限制 250MB

分析

假如当前在 u ,决策往哪棵子树走最优。

假设重心从 u 沿着边 (u,v) 移动距离 x ,可以得到:

f(x)=psubtree(v)ap(dispx)32+psubtree(v)ap(disp+x)32

Key observation :在 u 逼近 rt 的过程中,带权距离和不断变小。

感性理解一下(其实是博主不会证明):

由于 (kx)32(k+x)32 都是下凸函数,所以 f(x) 也是下凸函数。

由于下凸函数至多一个极小值点,所以 urt 函数值单调递减。

因此我们只需从任意一个点 u 出发,每次往可以让带权距离和变小的方向走一步(后面会证明如果存在必唯一)即可。如果往所有方向走都在变大,就说明我们已经找到最小值了。

判断往某个方向走是否可能变小,本质上是在判断 limx0Δ(f(x))<0 是否成立。

f(x)=32psubtree(v)apdispx+32psubtree(v)apdisp+xf(0)=32(p=1napdisp2psubtree(v)apdisp)

显然至多只有一个 v 能让 f(0)<0

注: f(0)<0 表示往 v 方向走带权距离和会变小,但不代表选择 v 点比 u 点更优。


接下来是一个常见套路:点分治重心移动。

我们花费 O(n) 的代价,却只移动了一步,难免有些浪费。

考虑点分治,假设当前在 u 并且要往 v 移动,我们直接移动到 v 所在连通块的分治重心,这样候选集大小减半, logn 次移动后即可找到真正的带权重心。

时间复杂度 O(nlogn)

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5,inf=1e9;
int n,s,u,v,w,rt,pos,tot;
double res=1e20;
double f[maxn],g[maxn];
int a[maxn];
int head[maxn],to[2*maxn],val[2*maxn],nxt[2*maxn];
int dp[maxn],sz[maxn];
bool vis[maxn];
void addedge(int u,int v,int w)
{
    nxt[++tot]=head[u],to[tot]=v,val[tot]=w,head[u]=tot;
}
void getroot(int u,int fa)
{
    dp[u]=0,sz[u]=1;
    for(int i=head[u];i!=0;i=nxt[i])
    {
        int v=to[i];
        if(vis[v]||v==fa) continue;
        getroot(v,u);
        dp[u]=max(dp[u],sz[v]),sz[u]+=sz[v];
    }
    dp[u]=max(dp[u],s-sz[u]);
    if(dp[u]<dp[rt]) rt=u;
}
void dfs(int u,int fa,int dep)
{
    f[u]=a[u]*sqrt(dep),g[u]=a[u]*pow(dep,1.5);
    for(int i=head[u];i!=0;i=nxt[i])
    {
        int v=to[i];
        if(v==fa) continue;
        dfs(v,u,dep+val[i]);
        f[u]+=f[v],g[u]+=g[v];
    }
}
void solve(int u)
{
    vis[u]=true,dfs(u,0,0);
    if(g[u]<res) res=g[u],pos=u;
    for(int i=head[u];i!=0;i=nxt[i])
    {
        int v=to[i];
        if(vis[v]) continue;
        if(f[u]-2*f[v]<0)
        {
            s=sz[v],rt=0,getroot(v,0),solve(rt);
            break;
        }
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n-1;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        addedge(u,v,w),addedge(v,u,w);
    }
    s=n,dp[0]=inf,getroot(1,0),solve(rt);
    printf("%d %.10lf",pos,res);
    return 0;
}

posted on   peiwenjun  阅读(5)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示