题目
题目背景
种瓜得瓜,种豆得豆,种树生树。
题目描述
有一棵树,对于每一条简单路径,定义其权值为路径上的点的点权形成的有序序列的
L
I
S
\rm LIS
LIS(最长上升子序列)。
求出所有路径中最大的权值,并输出这个权值。
数据范围与提示
对于
80
%
80\%
80% 的数据,
n
≤
2
×
1
0
5
n\le 2\times 10^5
n≤2×105 。
对于 100 % 100\% 100% 的数据, n ≤ 1.5 × 1 0 6 n\le 1.5\times 10^6 n≤1.5×106 。
思路
有一些比较显然的 O ( n log 2 n ) \mathcal O(n\log^2n) O(nlog2n) 做法,比如 d s u o n t r e e \tt dsu\; on\; tree dsuontree + + + B I T \tt BIT BIT 啥的。但是我们有 O ( n log n ) \mathcal O(n\log n) O(nlogn) 的做法,那就是 长链剖分。
序列上的 L I S / L D S \tt LIS/LDS LIS/LDS 大家都很会。那里面有一个神奇的数组, g ( i ) g(i) g(i) 表示长度为 i i i 的 L I S \tt LIS LIS 的子序列,结尾至少是多少。有一个经典的结论是, g ( i ) g(i) g(i) 是递增的。这个结论无论在树上,还是在序列上,都始终是成立的。这是因为长度为 i i i 的 L I S \tt LIS LIS 存在时,立刻存在 一个长度为 i − 1 i-1 i−1 的 L I S \tt LIS LIS,满足结尾更小。
那么可以搬到树上来了。用 g ( i ) g(i) g(i) 表示,子树内一个 “竖着” 的长度为 i i i 的 L I S \tt LIS LIS 的最小结尾。那么它仍然有单调性。转移就是子节点对应位置直接取 min \min min 。加入当前点、更新答案都是 O ( log n ) \mathcal O(\log n) O(logn) 的经典二分法。
代码
这里给出 O ( n log 2 n ) \mathcal O(n\log^2n) O(nlog2n) 的代码。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MaxN = 100005;
struct Edge{
int to, nxt;
};
Edge e[MaxN<<1];
int head[MaxN], cntEdge;
void addEdge(int a,int b){
e[cntEdge].to = b;
e[cntEdge].nxt = head[a];
head[a] = cntEdge ++;
}
int siz[MaxN], son[MaxN];
void scan(int x,int pre){
siz[x] = 1, son[x] = 0;
for(int i=head[x]; ~i; i=e[i].nxt)
if(e[i].to != pre){
scan(e[i].to,x);
siz[x] += siz[e[i].to];
if(siz[e[i].to] > siz[son[x]])
son[x] = e[i].to;
}
}
int len; // standard for BIT
struct BIT{
int c[MaxN]; bool opt;
void modify(int id,int v){
if(opt) id = len-id+1;
for(int i=id; i<=len; i+=(i&-i))
c[i] = max(c[i],v);
}
int query(int id){
if(opt) id = len-id+1;
int res = 0;
for(int i=id; i; i-=(i&-i))
res = max(res,c[i]);
return res;
}
void clear(int id){
if(opt) id = len-id+1;
for(int i=id; i<=len; i+=(i&-i))
c[i] = 0; // clear chain
}
};
BIT tre[2]; // same as dp
int val[MaxN];
// from parent to son, 0: LIS 1: LDS
int dp[MaxN][2], ans;
void update(int x,int pre){
ans = max(ans,tre[0].query(val[x]+1)+dp[x][1]);
ans = max(ans,tre[1].query(val[x]-1)+dp[x][0]);
for(int i=head[x]; ~i; i=e[i].nxt)
if(e[i].to != pre) update(e[i].to,x);
}
void insert(int x,int pre){
tre[0].modify(val[x],dp[x][0]);
tre[1].modify(val[x],dp[x][1]);
for(int i=head[x]; ~i; i=e[i].nxt)
if(e[i].to != pre) insert(e[i].to,x);
}
void erase(int x,int pre){
tre[0].clear(val[x]);
tre[1].clear(val[x]);
for(int i=head[x]; ~i; i=e[i].nxt)
if(e[i].to != pre) erase(e[i].to,x);
}
void dfs(int x,int pre,bool f){
for(int i=head[x]; ~i; i=e[i].nxt)
if(e[i].to != pre && e[i].to != son[x])
dfs(e[i].to,x,false);
if(son[x]) dfs(son[x],x,true);
dp[x][0] = tre[0].query(val[x]+1)+1;
dp[x][1] = tre[1].query(val[x]-1)+1;
tre[0].modify(val[x],dp[x][0]);
tre[1].modify(val[x],dp[x][1]);
for(int i=head[x]; ~i; i=e[i].nxt)
if(e[i].to != pre && e[i].to != son[x]){
update(e[i].to,x); // separate
insert(e[i].to,x); // add all
/* some paths contain x itself */ ;
dp[x][0] = tre[0].query(val[x]+1)+1;
dp[x][1] = tre[1].query(val[x]-1)+1;
tre[0].modify(val[x],dp[x][0]);
tre[1].modify(val[x],dp[x][1]);
}
ans = max(ans,max(dp[x][0],dp[x][1]));
if(!f) erase(x,pre); // erase all
}
int tmp[MaxN];
int liSan(int *l,int *r){
int *p = tmp;
for(int *i=l; i!=r; ++i)
*p = *i, ++ p;
sort(tmp,p);
p = unique(tmp,p);
for(int *i=l; i!=r; ++i)
*i = lower_bound(
tmp,p,*i)-tmp+1;
return p-tmp;
}
int main(){
tre[0].opt = 1, tre[1].opt = 0;
int n = readint();
for(int i=1; i<=n; ++i){
val[i] = readint();
head[i] = -1;
}
len = liSan(val+1,val+n+1);
for(int i=1,x,y; i<n; ++i){
x = readint(), y = readint();
addEdge(x,y), addEdge(y,x);
}
scan(1,0); dfs(1,0,false);
printf("%d\n",ans);
return 0;
}