CF490F 题解
CF490F 题解
思考了将近一整天的题目,现在写个题解。
题意
给定一个带权树,求出树上最长严格递增子序列的长度,首先考虑严格递增子段的做法,大概是直接进行树形 dp 就行,但是子序列的问题又会复杂一些。
分析
其实大概花上了半天去思考 树上启发式合并 的做法,但是实在是不知道该如何撤销轻儿子子树对全局数组的影响,最后还是去写了线段树合并,感觉自己确实是纯飞舞一个。
暴力
对于这道十年前的题,原本的数据范围是 \(n\le 6000\),那么就让比较暴力的做法有了通过的空间,处理的难点无非在于:这个子序列在树上所展现出来的路径可能是一条“折线”,这导致我们并不能很轻松地转移。所以不妨钦定一个节点为路径起点和终点,以其为根进行一次 dfs,用 \(O(n\log n)\) 的做法来计算 LIS,在本题的数据范围下即可通过。
拓展
如果说数据范围变成 \(n\le 10^5\),暴力的方法变得不可取,不过思路仍然可以沿用,这里我们对每一个节点都维护其记录其 子树信息 的线段树,对应下标为 \(i\) 的线段树叶节点记录的信息为:当前子树内以值为 \(i\) 结尾的最长 lis/lds 长度。
对于每一段合法的路径,我们在这条路径的 LCA 处考虑答案,也就自然想到了在每个节点为 dfs 的根节点的时候同时维护答案。
计算答案
假设对于权值是 \(a[x]\) 的节点 \(x\),我们计算出了其子树内关于 lis/lds 的信息,那么这个答案子序列的选取有两种情况:
- \(x\) 在子序列内,答案是 \(1+\max lis_{[1,a[x]-1]}+\max lds_{[a[x]+1,n]}\)。
- \(x\) 不在子序列中,答案是 $\max_{s,t\in subtree(x) \land a[s]<a[t]}lis_{a[s]}+lds_{a[t]} $,并且 \(s,t\) 不在同一个子树中。
1 是容易计算的,我们在合并子树的时候同时记录一下 当前子树的最大值,并同时在待合并子树的线段树上根据 \(a[x]\) 查询一下最大值,然后相加即可。
但是 2 并不是那么好算的,这相当于是一个偏序问题,考虑 cdq 分治把权值的限制做掉,幸运的是我们是以权值为下标来建立的线段树,所以在递归合并两棵线段树的时候,直接把左右儿子的 lis/lds 值拿出来更新答案就行。
其实这个线段树维护某些带偏序的最值的 trick 之前有做到过一两次,不过可能还是悟性不够高,没有能变到这个题目上来。
这也说明了为什么一定要这样定义 dp 数组,如果说我们采用 \(n\log n\) 计算 LIS 的 dp 定义,可以通过二分来计算情况1,但是情况2就没有什么办法来基于下标进行分治了。
更新 lis/lds
我们把所有子树内的 lis/lds 信息汇总到一起后,唯一会产生新答案的就是当前的根节点。
具体来说,查询 \([1,a[x]-1]\) 中的最大值 \(mlis\),并尝试用 \(mlis+1\) 更新 \(a[x]\) 处的 \(lis\) 值;查询 \([a[x]+1,n]\) 中的最大值 \(mlds\),并尝试用 \(mlds+1\) 更新 \(a[x]\) 处的 \(lds\) 值。
Code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int a[N],b[N],n;
vector<int> e[N];
int lis[N<<5],lds[N<<5],ls[N<<5],rs[N<<5],root[N],node;
inline void pu(int x)
{
lis[x]=max(lis[ls[x]],lis[rs[x]]);
lds[x]=max(lds[ls[x]],lds[rs[x]]);
}
inline void modify(int &x,int l,int r,int pos,int v,int *val)
{
if(!x)x=++node;
if(l==r)return val[x]=max(val[x],v),void();
int mid=l+r>>1;
if(pos<=mid)modify(ls[x],l,mid,pos,v,val);
else modify(rs[x],mid+1,r,pos,v,val);
pu(x);
}
inline int query(int x,int l,int r,int ql,int qr,int *val)
{
if(ql<=l&&r<=qr)return val[x];
int mid=l+r>>1;
if(qr<=mid)return query(ls[x],l,mid,ql,qr,val);
else if(ql>mid)return query(rs[x],mid+1,r,ql,qr,val);
else return max(query(ls[x],l,mid,ql,qr,val),query(rs[x],mid+1,r,ql,qr,val));
}
int ans=0;
inline int merge(int x,int y)
{
if(!x||!y)return x+y;
ans=max(lis[ls[x]]+lds[rs[y]],ans);
ans=max(lis[ls[y]]+lds[rs[x]],ans);
ls[x]=merge(ls[x],ls[y]),rs[x]=merge(rs[x],rs[y]);
return pu(x),x;
}
inline void dfs(int x,int fa)
{
int mlis=0,mlds=0;
for(int v:e[x])
{
if(v==fa)continue;
dfs(v,x);
int newlis=query(root[v],1,n,1,a[x]-1,lis),newlds=query(root[v],1,n,a[x]+1,n,lds);
ans=max(newlis+mlds+1,ans),ans=max(mlis+newlds+1,ans);
root[x]=merge(root[x],root[v]);
mlis=max(mlis,newlis),mlds=max(mlds,newlds);
}
modify(root[x],1,n,a[x],mlis+1,lis);
modify(root[x],1,n,a[x],mlds+1,lds);
}
inline void solve()
{
cin>>n;
for(int i=1;i<=n;++i)cin>>a[i],b[i]=a[i];
sort(b+1,b+n+1);
for(int i=1;i<=n;++i)a[i]=lower_bound(b+1,b+n+1,a[i])-b+1;
for(int i=1,u,v;i<n;++i)
{
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
n+=2;
dfs(1,0);
cout<<ans;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
return solve(),0;
}
本文来自博客园,作者:Hanggoash,转载请注明原文链接:https://www.cnblogs.com/Hanggoash/p/19039773

浙公网安备 33010602011771号