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 的信息,那么这个答案子序列的选取有两种情况:

  1. \(x\) 在子序列内,答案是 \(1+\max lis_{[1,a[x]-1]}+\max lds_{[a[x]+1,n]}\)
  2. \(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;
}
posted @ 2025-08-15 15:36  Hanggoash  阅读(4)  评论(0)    收藏  举报
动态线条
动态线条end