NC54585 小魂和他的數列

空白菌發表於2023-05-06

題目連結

題目

題目描述

一天,小魂正和一個數列玩得不亦樂乎。
小魂的數列一共有n個元素,第i個數為Ai。
他發現,這個數列的一些子序列中的元素是嚴格遞增的。
他想知道,這個數列一共有多少個長度為K的子序列是嚴格遞增的。

請你幫幫他,答案對998244353取模。

對於100%的資料,1≤ n ≤ 500,000,2≤ K ≤ 10,1≤ Ai ≤ 109。

輸入描述

第一行包含兩個整數n,K,表示數列元素的個數和子序列的長度。
第二行包含n個整數,表示小魂的數列。

輸出描述

一行一個整數,表示長度為K的嚴格遞增子序列的個數對998244353取模的值。

示例1

輸入

5 3
2 3 3 5 1

輸出

2

說明

兩個子序列分別是 2 3 3 5 1 和 2 3 3 5 1 。

題解

知識點:樹狀陣列,列舉,線性dp。

仿照最長上升子序列的狀態,設 \(f_{i,j}\) 為以第 \(i\) 個數結尾且長度為 \(j\) 的上升子序列個數,顯然是 \(O(n^2k)\) 的。其中可以最佳化的步驟是,查詢上一個比自己小的元素。對於最長上升子序列最佳化查詢的步驟,通常有三種方法,我們依次考慮是否適合用於這道題的狀態:

  1. 改變狀態,設 \(f_i\) 為長度為 \(i\) 的上升子序列的最小結尾數字,其有單調遞增的性質,因此每次新增數字,二分查詢最後一個小於自己數字的位置即可。但是,這個顯然不適合用來最佳化這道題的狀態。
  2. 最佳化查詢,用資料結構維護字首權值最大值,即以數字作為下標維護每個數字結尾的最大長度。這個方法是可以考慮的,我們將維護最大長度改為各個長度的上升子序列個數即可。
  3. 排序+最佳化查詢,將數字順序改為從小到大輸入,用資料結構維護字首最大值,即在原本的區間上維護每個位置結尾的最大長度,輸入順序保證了每次詢問的答案一定都是比自己小的數字構成的答案,都是可以接上的。這個方法同樣也是可以考慮的,我們將維護最大長度改為各個長度的上升子序列個數即可。

第二種方法需要先離散化,我們這裡使用的是第三種方法,先排序後最佳化查詢,複雜度上是沒有區別的。

需要注意的是,使用第三種方法從小到大列舉時,因為要求的是上升子序列,所以相等的數字不能直接更新到資料結構中,需要等到所有相等的數字都查詢完,才能一併更新。第二種方法由於直接維護權值關係,大小可以直接確定,則沒有這種情況。

時間複雜度 \(O(nk \log n)\)

空間複雜度 \(O(nk)\)

程式碼

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const int P = 998244353;

template<class T>
class Fenwick {
    int n;
    vector<T> node;

public:
    Fenwick(int _n = 0) { init(_n); }

    void init(int _n) {
        n = _n;
        node.assign(n + 1, T());
    }

    void update(int x, T val) { for (int i = x;i <= n;i += i & -i) node[i] += val; }

    T query(int x) {
        T ans = T();
        for (int i = x;i;i -= i & -i) ans += node[i];
        return ans;
    }
};

int k;
struct T {
    array<int, 17> f = {};
    T &operator+=(const T &x) {
        for (int i = 1;i <= k;i++) (f[i] += x.f[i]) %= P;
        return *this;
    }
};

pair<int, int> a[500007];
int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n >> k;
    for (int i = 1;i <= n;i++) {
        int x;
        cin >> x;
        a[i] = { x,i };
    }
    sort(a + 1, a + n + 1, [&](auto a, auto b) {return a.first < b.first;});

    int ans = 0;
    Fenwick<T> fw(n);
    vector<pair<int, array<int, 17>>> v;
    for (int i = 1;i <= n;i++) {
        if (a[i].first != a[i - 1].first) {
            for (auto [id, f] : v) fw.update(id, { f });
            v.clear();
        }
        auto res = fw.query(a[i].second).f;
        for (int j = k;j >= 1;j--) res[j] = res[j - 1];
        res[1] = 1;
        (ans += res[k]) %= P;
        v.push_back({ a[i].second,res });
    }
    cout << ans << '\n';
    return 0;
}

相關文章