HDOJ 6991 Increasing Subsequence

题目链接

HDOJ 6991 Increasing Subsequence (Minieye 杯联赛第四场 T7)

题目大意

定义极大上升子序列为「不为任意一个上升子序列的真子集」的上升子序列,给定一个长度为 (n) 的排列,求其极大上升子序列个数,答案对 (998244353) 取模。

(1leq nleq 10^5)

思路

观察 极大上升子序列 的定义,可以发现其等价于要求序列的相邻两项 (i,j)​​,满足 (forall i<k<j,;a_k<a_iigvee a_k>a_j)​​​ ,于是可以考虑 (dp),记 (dp_i) 为以 (a_i) 结尾的极大上升子序列的数量,则 (dp_j)(dp_i) 转移的条件如上,相当于在去除大于 (a_i) 的元素之后,(a_j) 是从 (i) 往前的后缀最大值。而初值 (dp_i=[a_i=min_{1leq jleq i}{a_j}]),当 (a_i) 是前缀最小值时才能作为一个极大上升子序列的开头,答案为所有后缀最大值位置的 (dp) 值之和。

朴素地做是 (O(n^2))​ 的,想了半天没有找到用数据结构优化转移的做法,难点在对于每个 (a_i)​ 转移位置构成的单调序列,它们之间没有什么有用的共同点,这个时候可以尝试换个角度,考虑使用分治。这样问题就转化成了对于区间 ([l,r])​,如何将左半区间的 (dp)​​​ 值转移到右半边上。

观察上图,对于当前在计算的点 (a_i),我们需要找到在 (i) 前面的右半区间中,最大的比 (a_i) 小的元素 (a_j),能够向 (dp_i) 转移的 (k) 都需要满足 (a_j<a_k<a_i),即两虚线所夹的区域,同时在左半区间内,这些可以转移的点还构成从右往左的单增序列,不在序列内的则无法转移。

观察到这些性质,肯定是要用单调数据结构来维护这些转移点,注意到右半区间「最大的比 (a_i) 小的点」以及左半区间随着红线上升,转移点时刻呈单减序列,可以想到用两个以 (a_i) 作为顺序,(i)​ 作为关键字的单调栈 (left,right) 来维护这些信息。具体来说,我们按照 (a_i) 从小到大逐个把点加入左/右半区间的单调栈中,右半区间加入一个 (a_i)​ 时,(right) 的顶部剩下来的即为 (j),然后在 (left) 中找到第一个 (>a_j) 的位置 (pos),则 (left) 栈中 ([pos,end]) 上的所有位置都可转移到 (dp_i)​,我们同时随着 (left) 维护一个前缀和即可 (O(1)) 进行转移。

二分 (pos)(O(log n)) 的,所以时间复杂度 (O(nlog^2n))

Code

#include<iostream>
#include<algorithm>
#include<vector>
#include<numeric>
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,b,a) for(int i = (b); i >= (a); i--)
#define N 101000
#define ll long long
#define mod 998244353
#define Inf 0x3f3f3f3f
using namespace std;

int a[N], n;
ll sum[N], dp[N];

bool cmp(int i, int j){ return a[i] < a[j]; }

void solve(int l, int r){
    if(l == r) return;
    int mid = (l+r)>>1;
    solve(l, mid);

    vector<int> pos(r-l+1);
    vector<int> left, right;
    iota(pos.begin(), pos.end(), l);
    sort(pos.begin(), pos.end(), cmp);
    for(int i : pos){
        if(i <= mid){
            while(!left.empty() && left.back() < i) left.pop_back();
            left.push_back(i);
            int siz = left.size();
            sum[siz] = sum[siz-1]+dp[i]; 
        } else{
            while(!right.empty() && right.back() > i) right.pop_back();
            if(left.empty()) continue;
            int lb = right.empty() ? 0 : lower_bound(left.begin(), left.end(), right.back(), cmp) - left.begin();
            (dp[i] += sum[left.size()] - sum[lb] + mod) %= mod;
            right.push_back(i);
        }
    }
    solve(mid+1, r);
}

int main(){
    ios::sync_with_stdio(false);
    int T; cin>>T;
    while(T--){
        cin>>n;
        rep(i,1,n) cin>>a[i];

        int mn = Inf;
        rep(i,1,n){
            if(mn > a[i]) mn = a[i], dp[i] = 1;
            else dp[i] = 0;
        }
        solve(1, n);
        int mx = 0;
        ll ans = 0;
        per(i,n,1) if(mx < a[i]) mx = a[i], (ans += dp[i]) %= mod;

        cout<< ans <<endl;
    }
    return 0;
}
原文地址:https://www.cnblogs.com/Neal-lee/p/15085094.html