bzoj2870(点分治+树状数组)

题解

(下文子树权值是指当前点到重心节点路径的最小值)

摁….卡线段树….选择树状数组吧(树状数组下标是权值 维护前缀区间路径长度的最大值)

因为是点对问题….点分治没跑了

我们考虑子树合并 对于大于当前结点权值的点 我们可以在$O(logn)$的情况下得到答案 但是对于小于当前权值的点我们没有比较高效的办法统计贡献 但是我们可以反着跑一遍 就能维护所有的答案了

时间复杂度 $O(nlog^2n)$

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <stack>
#include <queue>
#include <cmath>
#include <set>
#include <map>
#define mp make_pair
#define pb push_back
#define pii pair<int,int>
#define link(x) for(edge *j=h[x];j;j=j->next)
#define inc(i,l,r) for(int i=l;i<=r;i++)
#define dec(i,r,l) for(int i=r;i>=l;i--)
const int MAXN=3e5+10;
const double eps=1e-8;
#define ll long long
const int inf=1e9;
using namespace std;
struct edge{int t;edge*next;}e[MAXN<<1],*h[MAXN],*o=e;
void add(int x,int y){o->t=y;o->next=h[x];h[x]=o++;}
ll read(){
ll x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
return x*f;
}

int N,n,a[MAXN],base,key,rt,sz[MAXN],maxx[MAXN];
int ans2[MAXN];
bool vis[MAXN];
ll ANS;

int get_id(int x){return x&(-x);}
void update(int x,int t){
for(int i=x;i<=N;i+=get_id(i))ans2[i]=max(ans2[i],t);
}
int query(int x){
int ans=0;
for(int i=x;i>0;i-=get_id(i))ans=max(ans,ans2[i]);
return ans;
}

void clear(int x){
for(int i=x;i<=N;i+=get_id(i))ans2[i]=0;
}

void get_root(int x,int pre){
sz[x]=1;maxx[x]=0;
link(x){
if(vis[j->t]||j->t==pre)continue;
get_root(j->t,x);
sz[x]+=sz[j->t];maxx[x]=max(maxx[x],sz[j->t]);
}
maxx[x]=max(maxx[x],base-sz[x]);
if(key>maxx[x])key=maxx[x],rt=x;
}

int num[MAXN],dis[MAXN];
int st[MAXN],tot,St[MAXN],tot1,ans1;
void dfs(int x,int pre){
ans1=query(N-dis[x]+1);
if(ans1)ANS=max(ANS,1ll*(ans1+num[x]-1)*(dis[x]-1)),st[++tot]=x;
link(x){
if(vis[j->t]||j->t==pre)continue;
num[j->t]=num[x]+1;dis[j->t]=min(dis[x],a[j->t]);
dfs(j->t,x);
}
}

int p[MAXN],cnt;
void solve(int x){
vis[x]=1;tot1=0;cnt=0;
update(N-a[x]+1,1);
link(x){
if(vis[j->t])continue;
p[++cnt]=j->t;
tot=0;num[j->t]=2;dis[j->t]=min(a[j->t],a[x]);dfs(j->t,x);
inc(i,1,tot)update(N-dis[st[i]]+1,num[st[i]]),St[++tot1]=st[i];
}
inc(i,1,tot1)clear(N-dis[St[i]]+1);
update(N-a[x]+1,1);
dec(i,cnt,1){
tot=0;num[p[i]]=2;dis[p[i]]=min(a[p[i]],a[x]);dfs(p[i],x);
inc(j,1,tot)update(N-dis[st[j]]+1,num[st[j]]);
}
inc(i,1,tot1)clear(N-dis[St[i]]+1);
clear(N-a[x]+1);
link(x){
if(vis[j->t])continue;
key=inf;base=sz[j->t];get_root(j->t,0);
solve(rt);
}
}

int main(){
n=read();
inc(i,1,n)a[i]=read()+1,N=max(N,a[i]),ANS=max(ANS,1ll*(a[i]-1));
int x,y;
inc(i,2,n)x=read(),y=read(),add(x,y),add(y,x);
key=inf;base=n;get_root(1,0);
solve(rt);
printf("%lld\n",ANS);
return 0;
}

题目描述

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

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

Input

第一行$N$

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

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

Output

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