• 【CF566C】Logistical Questions(点分治)


    点此看题面

    • 给定一棵(n)个点的树,有点权和边权。
    • 定义两点间的距离为两点间边权和的(frac32)次方。
    • 求这棵树的带权重心。
    • (nle2 imes10^5)

    下凸函数

    显然,对于一条树上路径([a,b])中的点(x)(x)与另一点(i)之间的距离(d(x,i))是一个下凸函数。

    考虑对于一个点,(f(x)=sum_{i=1}^nd(x,i) imes a(x))是凸函数之和,那么它仍然是凸函数。

    所以,如果从树的重心向外扩展,(f(x))的变化必然是递增的。

    点分治

    考虑对于一条链的情况,我们可以采取二分。

    把二分扩展到树上,就变成了点分治。注意这里的点分治和通常所说的点分治并不完全一样。

    对于当前的根节点,从它向子节点走,最多只会有一个子节点可能使得(f(x))变大。

    至于如何判断是否变大,显然不能直接暴力求解。

    我们可以求出该函数的导数。设一个子节点的导数为(p(y)),总和为(s),如果((s-p(y))-p(y)=s-2p(y)<0),就说明向这个子节点走可能使得(f(x))变小,那么递归继续做即可。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 200000
    #define DB double
    #define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].v=z)
    using namespace std;
    int n,a[N+5],ee,lnk[N+5];struct edge {int to,nxt,v;}e[N<<1];
    namespace DotSolver//点分治
    {
    	int ans,rt,Sz[N+5],Mx[N+5],used[N+5];DB cur,res,p[N+5];
    	I void GetRt(CI x,CI lst,RI s)//找重心
    	{
    		Sz[x]=1,Mx[x]=0;for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&
    			!used[e[i].to]&&(GetRt(e[i].to,x,s),Sz[x]+=Sz[e[i].to],Mx[x]=max(Mx[x],Sz[e[i].to]));
    		Mx[x]=max(Mx[x],s-Sz[x]),Mx[x]<Mx[rt]&&(rt=x);
    	}
    	I void Calc(CI x,CI lst,CI d,DB& p)//遍历子树进行统计
    	{
    		cur+=pow(d,1.5)*a[x],p+=1.5*pow(d,0.5)*a[x];//cur统计答案,p统计导数
    		for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(Calc(e[i].to,x,d+e[i].v,p),0);
    	}
    	I void Work(RI x)//求解当前子树
    	{
    		RI i,y;DB s=cur=0;used[x]=1;
    		for(i=lnk[x];i;i=e[i].nxt) p[y=e[i].to]=0,Calc(y,x,e[i].v,p[y]),s+=p[y];//s统计导数总和
    		for((!ans||cur<res)&&(ans=x,res=cur),i=lnk[x];i;i=e[i].nxt)//更新答案
    			if(!used[e[i].to]&&s-2*p[y=e[i].to]<0) return GetRt(y,rt=0,Sz[y]),Work(rt);//走向可能使答案变大的点
    	}
    	I void Solve() {Mx[0]=1e9,GetRt(1,rt=0,n),Work(rt),printf("%d %.10lf
    ",ans,res);}
    }
    int main()
    {
    	RI i;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i);
    	RI x,y,z;for(i=1;i^n;++i) scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z);
    	return DotSolver::Solve(),0;
    }
    
    
  • 相关阅读:
    C#简单游戏外挂制作(以Warcraft Ⅲ为例)
    Push模式
    关于VS2005中GridView的自定义分页,单选、多选、排序、自增列的简单应用
    更改SQL表的所有者
    Microsoft Visual Studio 2005中使用水晶报表(非常棒)
    简单介绍一下水晶报表的推与拉两种模式
    SQL函数之四舍五入(转)
    如何制作一个多栏报表
    ASP.NET dropdownlist绑定数据源两种方法
    PUSH模式样板招式
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/CF566C.html
Copyright © 2020-2023  润新知