[CF597C Subsequences]

前言

这是一道数据结构优化 的入门题,但是也很有意思。

前置知识:线段树维护前缀和(区间加法,单点求值)

题解正文

题目大意:

给定一个长度为 的序列 以及一个数 ,求其长度为 的上升子序列的数量。

思路:

首先我们看到本题,不难想到经典问题:最长上升子序列。(但是其实关系不大)

在解最长上升子序列的时候我们一般用的是 的算法,但是实际上还存在一种被不屑的 求最长上升子序列的方法。

对于 的方法稍加魔改变形就可以得到本题的 做法。

:

设置状态:

表示前 个数中,以 结尾的长度为 的上升子序列的数量。

转移方程:

边界设置:

最终答案即:

上面这个方法我们需要枚举 ,以及子序列长度 ,还有枚举 前面的所有 ,总复杂度是 O 的,这是显然过不了本题的。

思考如何优化。

不难发现 只与 有关,可以类似于前缀和处理,但是前缀和每次修改需要 O 的复杂度,行不通。

于是考虑用 线段树/树状数组 优化这个过程。

看作是前缀和的下标,然后前缀和对应的值就是 ,然后线段树维护前缀和即可。

因为是按照 ~ 的顺序进行 的,我们很容易边 转移状态边修改,从而满足 ,于是就可以大胆的采用 棵线段树维护整个 过程,从而使得整个过程都满足

此题完结。

细节可见代码:

Code

#include <bits/stdc++.h>
using namespace std;
char buf[1 << 22] , *p1 = buf , * p2 = buf;(kaui)
#define int long long
#define mid ((L[x] + R[x]) >> 1)
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 1 << 21 , stdin) , p1 == p2) ? EOF : *p1++)
const int MAXN = 1e5 + 50;
int n,k,A[MAXN];
long long dp[MAXN][15],Ans;

inline int read() {
    int x = 0 , flag = 1;
    char ch = getchar();
    for( ; ch > '9' || ch < '0' ; ch = getchar()) if(ch == '-') flag = -1;
    for( ; ch >= '0' && ch <= '9' ; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0';
    return x * flag;
}

struct SegmentTree {//11棵线段树都写一遍不现实,用结构体就可以封装了
    int L[MAXN * 4],R[MAXN * 4],sum[MAXN * 4],laz[MAXN * 4];

    void build(int x,int l,int r) {//建树
        L[x] = l , R[x] = r; 
        laz[x] = sum[x] = 0;
        if(l == r) return ;
        build(x << 1 , l , mid);
        build(x << 1 | 1  , mid + 1 , r);
    }

    void ad(int x,int k) {//修改 
        laz[x] += k , sum[x] += k; 
        return ;
    }

    void pushdown(int x) {//下传标记
        if(laz[x] == 0) return ;
        ad(x << 1 , laz[x]);
        ad(x << 1 | 1 , laz[x]);
        laz[x] = 0;
        return ;
    }

    int Get(int x,int pos) {//单点求值
        if(L[x] == pos && R[x] == pos) return sum[x];
        pushdown(x);
        if(pos <= mid) return Get(x << 1 , pos);
        else return Get(x << 1 | 1, pos);
    }

    void change(int x,int l,int r,int k) {//区间修改
        if(L[x] >= l && R[x] <= r) {ad(x,k) ; return ;}
        if(l <= mid) change(x << 1 , l , r , k);
        if(r  > mid) change(x << 1 | 1 , l , r , k);
        return ;
    }
} T[12];

signed main() {
    n = read() , k = read();
    for(int i = 1 ; i <= n ; i ++) A[i] = read(),dp[i][1] = 1;
    if(k == 0) { cout << n ; return 0;}
    for(int i = 1 ; i <= k + 1 ; i ++) T[i].build(1, 1, n);
    for(int i = 1 ; i <= n ; i ++) {
        for(int j = 2 ; j <= min(i, k + 1) ; j ++)
        dp[i][j] += T[j - 1].Get(1,A[i]) ;//获得答案
        for(int j = 1 ; j <= min(i, k + 1) ; j ++)
        T[j].change(1,A[i] + 1 , n , dp[i][j]);//每次都修改
        Ans += dp[i][k + 1];//统计答案
    }
    cout << Ans;
    return 0;
}