题目链接
题目描述
一天,小魂正和一个数列玩得不亦乐乎。
小魂的数列一共有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)\) 的。其中可以优化的步骤是,查找上一个比自己小的元素。对于最长上升子序列优化查找的步骤,通常有三种方法,我们依次考虑是否适合用于这道题的状态:
第二种方法需要先离散化,我们这里使用的是第三种方法,先排序后优化查找,复杂度上是没有区别的。
需要注意的是,使用第三种方法从小到大枚举时,因为要求的是上升子序列,所以相等的数字不能直接更新到数据结构中,需要等到所有相等的数字都查询完,才能一并更新。第二种方法由于直接维护权值关系,大小可以直接确定,则没有这种情况。
时间复杂度 \(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; }