CF597C 子序列

1 CF597C 子序列

2 题目说明

时间限制 \(1s\) | 空间限制 \(256M\)

对于给定的 \(n\) 个不同元素组成的序列找出 \(k+1\) 个元素的上升子序列个数。结果最大不超过 \(8 \times 10^{18}\)

数据范围:\(1 ≤ n ≤ 10^5, 0 ≤ k ≤ 10\)

3 题解

最长上升子序列的 \(dp\) 状态为 \(dp_i\),表示以第 \(i\) 个数为结尾的最长上升子序列长度。这道题中增加了 \(k\) 这一限定条件,我们便相应地加上 \(j\) 这一维度,使 \(dp_{i, j}\) 表示长度为 \(i\) 且以 \(j\) 为结尾的上升子序列个数。我们不难推出转移:

\[dp_{i, j} = \sum_{l = 1, a_l < a_j}^{j-1} \limits dp_{i-1, l} \]

如果我们暴力求解,那么时间复杂度就是 \(O(n^2k)\),无法通过此题。枚举 \(i\)\(j\) 的循环看起来不怎么好优化,我们就来想想如何优化里面的循环。我们发现,找前 \(l-1\) 个数中小于第 \(l\) 个数的数这种问题可以利用权值线段树在 \(log\space n\) 的时间复杂度内解决。但是,我们的线段树只能把个数求出来,不能帮助我们计算具体数值。为了解决这个问题,我们可以把 \(dp_{i-1, l}\) 的值存储在 \(a_l\) 在线段树中的位置上,使得我们在统计时就可以直接加上 \(dp\) 值。但是我们现在这样仍然需要建 \(11\) 棵线段树:每棵线段树记录对于一个长度的所有 \(dp\) 值。为了节省空间,我们可以在每次计算完 \(dp\) 值后重新 \(build\) 一遍整棵线段树,然后在计算第 \(l\) 个数时把 \(dp_{i-1, l}\) 的值加给 \(a_l\) 这个数在权值线段树中对应的位置。这样,我们只需要一颗线段树就可以算出来所有的 \(dp\) 值。

最终的答案就是 \(\sum_{l = k}^{n} \limits dp_{k, l}\)

4 代码(空格警告):

#include <iostream>
using namespace std;
const int N = 1e5+10;
const int K = 15;
#define int long long
int n, k, aans;
int a[N], dp[K][N];
struct node
{
    int l, r, sum;
}t[N*4];
void build(int p, int l, int r)
{
    t[p].l = l;
    t[p].r = r;
    if (l == r)
    {
        t[p].sum = 0;
        return ;
    }
    int mid = (l+r)/2;
    build(p*2, l, mid);
    build(p*2+1, mid+1, r);
    t[p].sum = t[p*2].sum + t[p*2+1].sum;
}
void modify(int p, int pos, int d)
{
    if (t[p].l == t[p].r)
    {
        t[p].sum += d;
        return ;
    }
    int mid = (t[p].l + t[p].r) / 2;
    if (pos <= mid) modify(p*2, pos, d);
    if (pos > mid) modify(p*2+1, pos, d);
    t[p].sum = t[p*2].sum + t[p*2+1].sum;
}
int query(int p, int l, int r)
{
    if (t[p].l >= l && t[p].r <= r) return t[p].sum;
    int mid = (t[p].l + t[p].r) / 2;
    int ans = 0;
    if (l <= mid) ans += query(p*2, l, r);
    if (r > mid) ans += query(p*2+1, l, r);
    return ans;
}
signed main()
{
    cin >> n >> k;
    for (int i = 1; i <= n; i++) cin >> a[i];
    build(1, 1, n);
    for (int i = 1; i <= n; i++) dp[0][a[i]] = 1;
    for (int i = 1; i <= k; i++)
    {
        build(1, 1, n);
        for (int j = 1; j <= n; j++)
        {
            modify(1, a[j], dp[i-1][j]);
            dp[i][j] = query(1, 1, a[j] - 1);
        }
    }
    for (int i = k; i <= n; i++) aans += dp[k][i];
    cout << aans;
    return 0;
}

欢迎关注我的公众号:智子笔记

posted @ 2021-03-03 20:57  David24  阅读(104)  评论(0编辑  收藏  举报