peiwenjun's blog 没有知识的荒原

CF566C Logistical Questions 题解

题目描述

给定一棵 \(n\) 个点的树,点有点权 \(w_i\) ,边有边权 \(l_i\) ,两点间的距离为边权和的 \(\frac 32\) 次方。

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

数据范围

  • \(1\le n\le 2\cdot 10^5\)
  • \(0\le w_i\le 10^8,1\le l_i\le 1000\)

相对或绝对误差不得超过 \(10^{-6}\)

时间限制 \(\texttt{2s}\) ,空间限制 \(\texttt{250MB}\)

分析

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

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

\[f(x)=\sum_{p\in subtree(v)}a_p\cdot(dis_p-x)^\frac 32 +\sum_{p\notin subtree(v)}a_p\cdot(dis_p+x)^\frac 32\\ \]

\(\texttt{Key observation}\) :在 \(u\) 逼近 \(rt\) 的过程中,带权距离和不断变小。

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

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

由于下凸函数至多一个极小值点,所以 \(u\to rt\) 函数值单调递减。

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

判断往某个方向走是否可能变小,本质上是在判断 \(\lim_{x\to 0}\Delta(f(x))\lt 0\) 是否成立。

\[f'(x)=-\frac 32\sum_{p\in subtree(v)}a_p\sqrt{dis_p-x}+\frac 32\sum_{p\notin subtree(v)}a_p\sqrt{dis_p+x}\\ f'(0)=\frac 32\bigg(\sum_{p=1}^na_p\sqrt{dis_p}-2\sum_{p\in subtree(v)}a_p\sqrt{dis_p}\bigg)\\ \]

显然至多只有一个 \(v\) 能让 \(f'(0)\lt 0\)

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


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

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

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

时间复杂度 \(\mathcal O(n\log n)\)

#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 2022-06-21 20:25  peiwenjun  阅读(20)  评论(0)    收藏  举报

导航