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\) ,可以得到:
\(\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\) 是否成立。
显然至多只有一个 \(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;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/16398236.html
浙公网安备 33010602011771号