把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【CF1303G】Sum of Prefix Sums(点分治+李超线段树)

点此看题面

  • 一棵\(n\)个点的树,第\(i\)个点权值为\(a_i\)
  • 假设一条路径上的点权依次为\(b_{1\sim k}\),则这条路径的权值为\(\sum_{i=1}^ki\times b_i\)
  • 对于所有树上路径,求出其中最大的权值。
  • \(n\le1.5\times10^5,a_i\le10^6\)

点分治+李超线段树

对于这种询问范围为所有树上路径问题,很容易想到点分治。

核心问题是如何合并两条路径。

假设一条从下往上到当前根的路径点数为\(c_1\),权值为\(s_1\);一条由当前根从上往下的路径点权和为\(t_2\),权值为\(s_2\)。那么这两条路径合并后的权值就是\(s_1+s_2+c_1\times t_2\)

也就是说,每条从上往下的路径可以看作一个一次函数\(t_2x+s_2\),而对于每条从下往上的路径我们询问\(x=c_1\)时的最大\(y\)值再加上\(s_1\)更新答案。

显然可以套用李超线段树解决。

代码:\(O(nlog^2n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 150000
#define V 150000000000LL
#define LL long long
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,a[N+5],ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	char oc,FI[FS],*FA=FI,*FB=FI;
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
struct Seg
{
	LL k,b;I Seg(Cn LL& x=0,Cn LL& y=0):k(x),b(y){}
	I LL operator () (Cn LL& x) Cn {return k*x+b;}
};
class SegmentTree
{
	private:
		#define PT CI l=1,CI r=n,CI rt=1
		#define LT l,mid,rt<<1
		#define RT mid+1,r,rt<<1|1
		Seg P[N<<2];
	public:
		I void Cl(PT) {if(!P[rt].k) return;if(P[rt]=Seg(),l==r) return;RI mid=l+r>>1;Cl(LT),Cl(RT);}//清空
		I LL Q(CI x,PT) {RI mid=l+r>>1;return P[rt].k?max(l^r?(x<=mid?Q(x,LT):Q(x,RT)):0,P[rt](x)):0;}//询问x处的最大y值
		I void A(Seg s,PT)//加入一条线段
		{
			if(!P[rt].k) return (void)(P[rt]=s);RI mid=l+r>>1;s(mid)>P[rt](mid)&&(swap(s,P[rt]),0);//记录较优的线段
			if(l==r) return;s(l)>P[rt](l)&&(A(s,LT),0),s(r)>P[rt](r)&&(A(s,RT),0);//进入未被完全碾压的子区间
		}
}S;
namespace T//点分治
{
	int u[N+5];LL ans;
	int rt,Sz[N+5],Mx[N+5];I void GetRt(CI x,CI lst,RI sz)//找重心
	{
		Sz[x]=1,Mx[x]=0;for(RI i=lnk[x];i;i=e[i].nxt) !u[e[i].to]&&
			e[i].to^lst&&(GetRt(e[i].to,x,sz),Sz[x]+=Sz[e[i].to],Mx[x]=max(Mx[x],Sz[e[i].to]));
		(Mx[x]=max(Mx[x],sz-Sz[x]))<Mx[rt]&&(rt=x);
	}
	I void U(CI x,CI lst,RI c,LL t,LL s)//从上往下的路径
	{
		S.A(Seg(t+=a[x],s+=1LL*(++c)*a[x]));//把这个一次函数修加到线段树上
		for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&!u[e[i].to]&&(U(e[i].to,x,c,t,s),0);
	}
	I void Q(CI x,CI lst,RI c,LL t,LL s)//从下往上的路径
	{
		++c,s+=(t+=a[x]),ans=max(ans,S.Q(c)+s);//在线段树上询问
		for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&!u[e[i].to]&&(Q(e[i].to,x,c,t,s),0);
	}
	int T,St[N+5];I void Solve(RI x)//点分治
	{
		RI i;for(u[x]=1,S.Cl(),i=lnk[x];i;i=e[i].nxt) !u[e[i].to]&&//正着搞一遍,最后考虑当前根
			(Q(St[++T]=e[i].to,x,1,a[x],a[x]),U(e[i].to,x,0,0,0),0);ans=max(ans,S.Q(1)+a[x]);
		S.Cl(),S.A(Seg(a[x],a[x]));W(T) Q(St[T],x,0,0,0),U(St[T--],x,1,a[x],a[x]);//反着搞一遍,首先插入当前根
		for(i=lnk[x];i;i=e[i].nxt) !u[e[i].to]&&(GetRt(e[i].to,rt=0,Sz[e[i].to]),Solve(rt),0);//进入分治子树
	}
}
int main()
{
	RI i,x,y;for(read(n),i=1;i^n;++i) read(x,y),add(x,y),add(y,x);for(i=1;i<=n;++i) read(a[i]);
	using namespace T;return Mx[0]=1e9,GetRt(1,rt=0,n),Solve(rt),printf("%lld\n",ans),0;
}
posted @ 2021-07-12 15:48  TheLostWeak  阅读(49)  评论(0编辑  收藏  举报