[FJOI2021模拟]树升数

220 篇文章 2 订阅
98 篇文章 0 订阅

题目

题目背景
种瓜得瓜,种豆得豆,种树生树。

题目描述
有一棵树,对于每一条简单路径,定义其权值为路径上的点的点权形成的有序序列的 L I S \rm LIS LIS(最长上升子序列)。

求出所有路径中最大的权值,并输出这个权值。

数据范围与提示
对于 80 % 80\% 80% 的数据, n ≤ 2 × 1 0 5 n\le 2\times 10^5 n2×105

对于 100 % 100\% 100% 的数据, n ≤ 1.5 × 1 0 6 n\le 1.5\times 10^6 n1.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 i1 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值