Codeforces 566C - Logistical Questions(点分治)
神仙题 %%%
首先考虑对这个奇奇怪怪的 \(t^{3/2}\) 进行一番观察。考虑构造函数 \(f(x)=ax^{3/2}+b(d-x)^{3/2}\),其中 \(a,b,d\) 为参数,将其求个导 \(f'(x)=\dfrac{3}{2}(ax^{1/2}-b(d-x)^{1/2})\),不难发现 \(f'(0)=-\dfrac{3}{2}b\sqrt{d}<0,f'(d)=\dfrac{3}{2}a\sqrt{d}>0\),而在 \([0,d]\) 中随着 \(x\) 的增大,\(ax^{1/2}\) 随之增大,\(b(d-x)^{1/2}\) 随之减小,故 \(f'(x)\) 在 \([0,d]\) 中为严格增函数,即 \(\exist x_0\in[0,d],f'(x_0)=0\),故 \(f(x)\) 在 \([0,d]\) 中为严格下凸函数。
这能给我们什么启示呢?考虑将这东西与树联系起来,对于树上任意一条边 \((u,v)\),假设 \(w_u=a,w_v=b\),边的距离为 \(d\),并且我们这个带权重心不一定刚好落在顶点上——它可以落在任意一条边的任意一点,那么假设我们取了 \((u,v)\) 边上一点,距离 \(u\) 为 \(x\),这样 \(u,v\) 两点的权值之和恰好就是上文中提到的函数 \(f(x)\),而 \(f(x)\) 为严格下凸函数可知必定存在 \((u,v)\) 上一点 \(t\),使得当我们将重心定在 \(t\) 时候,\(u,v\) 两点贡献的权值之和最小,并且离 \(t\) 越远,\(u,v\) 两点贡献的权值之和最大。
将这个结论扩展到整棵树依旧成立,我们还是假设重心可以落在任意一条边的任意一点,那么对于重心 \(G\),我们在不断远离 \(G\) 的过程中,所有点贡献权值之和必然是不断增大的;反之当我们不断靠近 \(G\) 的过程中所有点贡献的权值之和必然是不断减小的。或者说得更通俗些,现在有一枚棋子,摆放在树的任意一个位置,我们现在要沿着树上的边移动这枚棋子,那么上述结论说的是这样一件事:如果我们正在将这枚棋子向 \(G\) 的方向移动,那么权值之和是处于不断减小的过程中,否则权值之和是处于不断增大的过程中。
我们就考虑根据这个“移动棋子”的思想解决这道题。我们假设现在有一枚棋子摆放在 \(u\) 号节点,对于 \(u\) 的任意一个邻居 \(v\),我们考虑将 \(u\) 沿着 \(v\) 的方向移动会产生什么影响,我们假设 \(u\) 沿着 \(v\) 方向移动了 \(x\) 的距离,\(u\) 到每个节点 \(v\) 的距离为 \(d_v\),那么显然移动完之和权值之和 \(g_v(x)=\sum\limits_{t\in\text{subtree}(v)}a_t(d_t-x)^{3/2}+\sum\limits_{t\notin\text{subtree}(v)}a_t(d_t+x)^{3/2}\)。我们还可以注意到 \(x=0\) 时 \(g_v(x)\) 的值就是将重心定在 \(u\) 点时的权值,而假设重心 \(G\) 就在 \(v\) 的子树(含 \((u,v)\) 的边上),根据上面的结论有我们将 \(u\) 移动到 \(v\) 的过程中权值呈现减小的趋势,那么必然有 \(g_v(0)\) 的瞬时变化量为负,将 \(g_v(x)\) 求个导可得 \(g_v'(x)=-\sum\limits_{t\in\text{subtree}(v)}\dfrac{3}{2}a_t(d_t-x)^{1/2}-\sum\limits_{t\notin\text{subtree}(v)}\dfrac{3}{2}a_t(d_t+x)^{1/2}\),我们对于每个 \(v\) 进行一遍 DFS 求出 \(g'_v(0)\) 的值,然后找到 \(g_v'(0)<0\) 的 \(v\) 并将棋子移动到 \(v\) 即可,根据之前的推论这样的 \(v\) 是唯一的。
这样复杂度是平方的,过不去,等同于暴力。不过考虑有个东西叫点分治,假设我们预期将棋子移动到 \(v\) 的位置,那么我们可以确定重心一定在 \(v\) 的子树(包括 \((u,v)\) 的边上),我们就每次找出 \(v\) 子树的重心 \(G\)(这里的重心就是普通的重心分治里的重心),将棋子移动到 \(G\) 即可,根据重心分治的复杂度可知这样是 \(n\log n\) 的。
最后注意要每移动一次更新一次答案,不要直接输出最后一次到达的位置。
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=2e5;
const int INF=0x3f3f3f3f;
int n,a[MAXN+5],hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],val[MAXN*2+5],ec=0;
void adde(int u,int v,int w){to[++ec]=v;val[ec]=w;nxt[ec]=hd[u];hd[u]=ec;}
bool vis[MAXN+5];int siz[MAXN+5],mx[MAXN+5],cent=0;
int ans=0;double mn=1e25,sum[MAXN+5],sum1=0,sum2=0;
void findcent(int x,int f,int totsiz){
mx[x]=0;siz[x]=1;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f||vis[y]) continue;
findcent(y,x,totsiz);siz[x]+=siz[y];
chkmax(mx[x],siz[y]);
} chkmax(mx[x],totsiz-siz[x]);
if(mx[x]<mx[cent]) cent=x;
}
void calc(int x,int f,int rt,int dep){
sum1+=1.0*a[x]*dep*sqrt(dep);
sum2+=1.0*a[x]*sqrt(dep)*3/2;
sum[rt]+=1.0*a[x]*sqrt(dep)*3/2;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e],z=val[e];if(y==f) continue;
calc(y,x,rt,dep+z);
}
}
void divcent(int x){
if(vis[x]) return;vis[x]=1;sum1=sum2=0;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e],z=val[e];sum[y]=0;calc(y,x,y,z);
}
if(sum1<mn) mn=sum1,ans=x;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e],z=val[e];
if(sum2-sum[y]*2.0<0){
cent=0;findcent(y,x,siz[y]);divcent(cent);
return;
}
}
}
int main(){
scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1,u,v,w;i<n;i++) scanf("%d%d%d",&u,&v,&w),adde(u,v,w),adde(v,u,w);
mx[0]=INF;cent=0;findcent(1,0,n);divcent(cent);
printf("%d %.15lf\n",ans,mn);
return 0;
}