【BZOJ2870】最长道路(边分治)

时间:2023-03-09 16:52:04
【BZOJ2870】最长道路(边分治)

【BZOJ2870】最长道路(边分治)

题面

BZOJ权限题

Description

H城很大,有N个路口(从1到N编号),路口之间有N-1边,使得任意两个路口都能互相到达,这些道路的长度我们视作一样。每个路口都有很多车辆来往,所以每个路口i都有一个拥挤程度v[i],我们认为从路口s走到路口t的痛苦程度为s到t的路径上拥挤程度的最小值,乘上这条路径上的路口个数所得的积。现在请你求出痛苦程度最大的一条路径,你只需输出这个痛苦程度。

简化版描述:

给定一棵N个点的树,求树上一条链使得链的长度乘链上所有点中的最小权值所得的积最大。

其中链长度定义为链上点的个数。

Input

第一行N

第二行N个数分别表示1~N的点权v[i]

接下来N-1行每行两个数x、y,表示一条连接x和y的边

Output

一个数,表示最大的痛苦程度。

Sample Input

3

5 3 5

1 2

1 3

Sample Output

10

【样例解释】

选择从1到3的路径,痛苦程度为min(5,5)*2=10

题解

看到这个题就先想到了点分治。

然而对于重心的子树而言合并两条链是一件很蛋疼的事情。(不过确实是可以做的)

所以换种方法来考虑。

点分治是考虑过一个点的所有路径的答案。

这次考虑边,考虑经过这条边的所有路径的答案。

类似点分治,我们需要每次找出一条边来,使得其左右两侧的子树大小最接近,然后处理过这条边的答案然后把这条边删掉左右递归处理。

不难发现在菊花图上随便卡烂。

那么来优化,如果一个点存在多个儿子,就类似线段树一样给他建立若干虚点形成一个二叉树,这样子点数最多翻倍,而菊花图就卡不烂了。

考虑如何合并左右两条边,把所有链给取下来之后按照链上最小权值从大往小排序。

这样子直接枚举其中一条边,强制在另外一侧中选一个权值比他大的链,那么就可以求出其中的最大长度组合。

注意一下虚点构出来之后,其权值等于其父亲的权值,虚点连边的边权设为\(0\),而树边边权设为\(1\),这样子就可以维护链的长度了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define MAX 200200
#define ll long long
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
int n,N,v[MAX];ll ans;
struct Line{int v,next,w;}e[MAX<<1];
int h[MAX],cnt=1;
inline void Add(int u,int v,int w){e[cnt]=(Line){v,h[u],w};h[u]=cnt++;}
vector<int> son[MAX];
void dfs(int u,int ff)
{
for(int i=h[u];i;i=e[i].next)
if(e[i].v!=ff)
son[u].push_back(e[i].v),dfs(e[i].v,u);
}
void ReBuild()
{
cnt=2;memset(h,0,sizeof(h));
for(int i=1;i<=n;++i)
{
int l=son[i].size();
if(l<=2)
for(int j=0;j<l;++j)
{
Add(i,son[i][j],son[i][j]<=N);
Add(son[i][j],i,son[i][j]<=N);
}
else
{
int s1=++n,s2=++n;v[s1]=v[s2]=v[i];
Add(i,s1,0);Add(s1,i,0);Add(i,s2,0);Add(s2,i,0);
for(int j=0;j<l;++j)
if(j&1)son[s1].push_back(son[i][j]);
else son[s2].push_back(son[i][j]);
}
}
}
int size[MAX];
bool vis[MAX];
int rt,mx,Size;
void getroot(int u,int ff,int Size)
{
size[u]=1;
for(int i=h[u];i;i=e[i].next)
{
int v=e[i].v;if(vis[i>>1]||v==ff)continue;
getroot(v,u,Size);size[u]+=size[v];
int ret=max(size[v],Size-size[v]);
if(mx>ret)mx=ret,rt=i;
}
}
struct Pair{int l,v;}S[2][MAX];
bool operator<(Pair a,Pair b){return a.v>b.v;}
int top[2];
void dfs(int u,int ff,int len,int mn,int opt)
{
mn=min(mn,v[u]);S[opt][++top[opt]]=(Pair){len,mn};
for(int i=h[u];i;i=e[i].next)
if(!vis[i>>1]&&e[i].v!=ff)
dfs(e[i].v,u,len+e[i].w,mn,opt);
}
void Divide(int u,int Size)
{
mx=1e9;getroot(u,0,Size);
if(mx>=1e9)return;vis[rt>>1]=true;
top[0]=top[1]=0;
dfs(e[rt].v,0,0,1e9,0);
dfs(e[rt^1].v,0,0,1e9,1);
sort(&S[0][1],&S[0][top[0]+1]);
sort(&S[1][1],&S[1][top[1]+1]);
for(int i=1,j=1,mx=0;i<=top[0];++i)
{
while(j<=top[1]&&S[1][j].v>=S[0][i].v)mx=max(mx,S[1][j++].l);
if(j>1)ans=max(ans,1ll*(mx+S[0][i].l+1+e[rt].w)*S[0][i].v);
}
for(int i=1,j=1,mx=0;i<=top[1];++i)
{
while(j<=top[0]&&S[0][j].v>=S[1][i].v)mx=max(mx,S[0][j++].l);
if(j>1)ans=max(ans,1ll*(mx+S[1][i].l+1+e[rt].w)*S[1][i].v);
}
int nw=rt,S2=Size-size[e[rt].v];
Divide(e[nw].v,size[e[rt].v]);
Divide(e[nw^1].v,S2);
}
int main()
{
n=read();N=n;
for(int i=1;i<=n;++i)v[i]=read();
for(int i=1;i<n;++i)
{
int u=read(),v=read();
Add(u,v,1);Add(v,u,1);
}
dfs(1,0);ReBuild();
Divide(1,n);
printf("%lld\n",ans);
return 0;
}