Loading

题解:P11106 [ROI 2023] 峰值 (Day 1)

P11106

这题很妙阿。
直接暴力 DP,设计出来的是三维的,直接 T 飞。考虑通过观察性质来优化。
我们定义 \(q\) 的前缀最大值是 \(Q\)\(r\) 的前缀最小值是 \(R\)
注意到题目给出的是一个排列,我们取 \(Q\)\(R\) 值交界的地方 \(i\),那么 \((i,n]\) 的这段区间中,我们就只需要考虑 LIS 和 LDS 了,因为多出来的部分如果小于 \(a_i\) 就可以放到 LIS 里面,大于 \(a_i\) 就可以放到 LDS 里面,这个直接线段树倒着扫即可。
那么考虑 \([1,i)\) 这段前缀如何摆放,小于 \(a_i\) 的,全部摆放在 \(q\) 里,其它的放在 \(r\) 里就行了。我们定义 \(f_i\) 是以 \(a_i\) 为结尾,由前面全部小于等于 \(a_i\) 组成的子序列的峰值数,\(g_i\) 就是另外一个序列的反峰值数。这个可以用 set 维护,很简单,看代码。
这个时候考虑 \(a_i\) 放在哪个序列中。我们先考虑放在 \(q\) 里(递增的)。那么此时 \(Q\)\(R\) 分别是 \(a_i\) 和前面大于 \(a_i\) 的最小值 \(nxt\)。因为我们要保证 \(Q\)\(R\)\(i\) 之后值域相交,那么必须找到一个数 \(m\) 使得 \(q\) 在之后选最小值比 \(m\) 大的 LIS,\(r\) 在之后选最大值比 \(m\) 小的 LDS。这个 \(m\) 其实一共有两种可能,分别是 \(a_i\)\(nxt\),读者可以自行思考。对于 \(a_i\) 放在 \(r\) 里的情况同理。
最后对于每一个 \(i\) 答案就是 \(f_i + g_i - 1 + len_{lis} + len_{lds}\),取最大值就行了。

//to kill a living book
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+7;
int n,a[N],f[N],g[N],pos[N],pre[N],nxt[N];
struct Misaka{
    int v[N<<2];
    #define ls (x<<1)
    #define rs (x<<1|1)
    #define mid (l+r>>1)
    void build(int x,int l,int r){
        if(l==r) return v[x]=0,void();
        build(ls,l,mid),build(rs,mid+1,r);
        v[x]=max(v[ls],v[rs]);
    }
    void upd(int x,int l,int r,int q,int k){
        if(l==r) return v[x]=k,void();
        if(q<=mid) upd(ls,l,mid,q,k);
        else upd(rs,mid+1,r,q,k);
        v[x]=max(v[ls],v[rs]);
    }
    int get(int x,int l,int r,int ql,int qr){
        if(ql<=l&&r<=qr) return v[x];
        int res=0;
        if(ql<=mid) res=max(res,get(ls,l,mid,ql,qr));
        if(mid<qr) res=max(res,get(rs,mid+1,r,ql,qr));
        return res;
    }
};Misaka lis,lds;
void solve(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i],pos[a[i]]=i;
    set<int> s;
    s.insert(0),s.insert(n+1);
    pos[0]=pos[n+1]=f[0]=g[0]=0;
    for(int i=1;i<=n;i++){
        auto R=s.upper_bound(a[i]);
        auto L=R; L--;
        f[i]=f[pos[*L]]+1,g[i]=g[pos[*R]]+1;
        pre[i]=(*L),nxt[i]=(*R);
        s.insert(a[i]);
    }
    lis.build(1,0,n+1),lds.build(1,0,n+1);
    int res=0;
    for(int i=n;i>=1;i--){
        int cur=0;
        cur=max({cur,lis.get(1,0,n+1,nxt[i],n+1)+lds.get(1,0,n+1,0,nxt[i])});
        cur=max({cur,lis.get(1,0,n+1,a[i],n+1)+lds.get(1,0,n+1,0,a[i])});
        cur=max({cur,lis.get(1,0,n+1,pre[i],n+1)+lds.get(1,0,n+1,0,pre[i])});
        lis.upd(1,0,n+1,a[i],lis.get(1,0,n+1,a[i],n+1)+1);
        lds.upd(1,0,n+1,a[i],lds.get(1,0,n+1,0,a[i])+1);
        res=max(res,cur+f[i]+g[i]-1);
    }
    cout<<res<<"\n";
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int t; cin>>t;
    while(t--) solve();
    return 0;
}

提供一个好理解的样例手玩。

1
6
3 1 4 2 6 5
posted @ 2026-02-24 14:40  GE9x  阅读(29)  评论(2)    收藏  举报