【BZOJ2870】最長道路(邊分治)
題面
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;
}