【hdu 6991】Increasing Subsequence (CDQ分治)

题目链接

题目大意

给定一个 1 ∼ n 1\sim n 1n 的排列 a 1 , a 2 , . . . , a n a_1, a_2, ...,a_n a1,a2,...,an ,求极大上升子序列(即不存在真包含它的上升子序列)的个数。( n ≤ 1 0 5 n\le 10^5 n105

思路

首先有一个 O ( n 2 ) O(n^2) O(n2) 的做法:
f j f_j fj a 1 , a 2 , . . . , a i a_1, a_2, ..., a_i a1,a2,...,ai 中以 a i a_i ai 为结尾的极大上升子序列个数。
然后 f j f_j fj f i f_i fi 有贡献( f i + = f j f_i+=f_j fi+=fj)当且仅当 a j < a i a_j<a_i aj<ai ∄ k ( j < k < i ) , a j < a k < a i \not\exists k(j<k<i), a_j<a_k<a_i k(j<k<i),aj<ak<ai
最后设 a n + 1 = n + 1 a_{n+1}=n+1 an+1=n+1 ,则 f n + 1 f_{n+1} fn+1 即为答案。

然后发现可以利用CDQ分治进行优化。
考虑 [ l , m ] [l, m] [l,m] [ m + 1 , r ] [m+1, r] [m+1,r] 中各元素的贡献,可以从小到大考虑 [ l , r ] [l, r] [l,r] 中的各个数,并维护两个单调栈, [ l , m ] [l, m] [l,m] 的元素下标递减, [ m + 1 , r ] [m+1, r] [m+1,r] 的元素下标递增。
i ∈ [ m + 1 , r ] i\in [m+1, r] i[m+1,r] ,找到 [ m + 1 , r ] [m+1, r] [m+1,r] 栈中 a i a_i ai 左侧最靠右的元素(即退栈后的栈顶) a j a_j aj
然后找到 [ l , m ] [l, m] [l,m] 中第一个比 a j a_j aj 大的和最后一个比 a i a_i ai 小的(即栈顶)元素。 [ l , m ] [l, m] [l,m] 栈中它们之间的元素和即 [ l , m ] [l, m] [l,m] f i f_i fi 的贡献。
在这里插入图片描述

代码

#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l; i <= r; ++i)
#define per(i, r, l) for (int i = r; i >= l; --i)
using namespace std;
const int inf = 0x3fffffff;
const int N = 100005;
const int mod = 998244353;
int T;
int n;
class node {
   public:
    int id, val;
    bool operator<(const node &rhs) const { return val < rhs.val; }
} a[N];
int dp[N];
void init() {
    int mx = inf;
    rep(i, 1, n) {
        if (a[i].val < mx) {
            mx = a[i].val;
            dp[i] = 1;
        } else
            dp[i] = 0;
    }
}
void solve(int l, int r) {
    if (l == r) return;
    int mid = (l + r) / 2;
    solve(l, mid);

    sort(a + l, a + r + 1);
    static node sta1[N], sta2[N];
    static int top1, top2;
    static int s[N];
    top1 = top2 = 0;
    rep(i, l, r) {
        // printf("%d ", a[i].val);
        if (a[i].id <= mid) {
            while (top1 && sta1[top1].id < a[i].id) {
                --top1;
            }
            ++top1;
            sta1[top1] = a[i];
            s[top1] = (s[top1 - 1] + dp[a[i].id]) % mod;
        } else {
            while (top2 && sta2[top2].id > a[i].id) {
                --top2;
            }
            int j = lower_bound(sta1 + 1, sta1 + top1 + 1, sta2[top2]) - sta1;
            dp[a[i].id] = (1ll * dp[a[i].id] + s[top1] - s[j - 1] + mod) % mod;
            ++top2;
            sta2[top2] = a[i];
        }
    }
    // printf("\n");

    sort(a + l, a + r + 1, [](node x, node y) { return x.id < y.id; });

    solve(mid + 1, r);
}
int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        rep(i, 1, n) {
            a[i].id = i;
            scanf("%d", &a[i].val);
        }
        ++n;
        a[n].id = a[n].val = n;
        init();
        solve(1, n);
        printf("%d\n", dp[n]);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值