ทางเชือกดิ่งลง

o59_oct_c2_dslope

โจทย์ต้องการให้เราหา ลำดับลด (Decreasing Subsequence) ที่มีความยาว kk จากลำดับความยาว nn ที่กำหนดมาให้

Dynamic Programming

นิยาม dpi,j,ldp_{i,j,l} = จำนวน ลำดับลด ( Decreasing Subsequence ) ที่มีความยาว jj ตัวและลงท้ายด้วย ll ของลำดับ ii ตัวแรก

สมมุติว่าปัจจุบัน เรากำลังพิจารณาลำดับ i+1i+1 ตัวแรก การ transition จาก dpdp ของลำดับ ii ตัวแรก สามารถทำได้โดยการเพิ่มจำนวนวิธีที่เรานำ ai+1a_{i+1} มาต่อท้ายลำดับลดก่อนหน้านี้ทุกลำดับที่ลงท้ายด้วยตัวเลขที่มีค่ามากกว่า ai+1a_{i+1} เข้าไปใน dpdp ของลำดับ ii ตัวแรก หรือก็คือ

dpi+1,j,l={1if j=1 and l=ai+1dpi,j,l+m=ai+1+1ndpi,j1,motherwisedp_{i+1,j,l} = \begin{cases} 1 & \text{if } j = 1 \text{ and } l = a_{i+1}\\ dp_{i,j,l} + \sum_{m=a_{i+1}+1}^{n} dp_{i,j-1,m} & \text{otherwise} \end{cases}

คำตอบของปัญหาที่เราต้องการ คือ ลำดับลดความยาว kk หรือก็คือ m=1ndpn,m,k\sum_{m=1}^{n} dp_{n,m,k} นั่นเอง เราสามารถคำนวณคำตอบของปัญหาด้วยวิธีนี้ได้ในเวลา O(n3k)\mathcal{O}(n^3k)

Fenwick Tree

สังเกตว่าวิธีที่กล่าวมามีปัญหาคือเราต้องพิจารณาทุก ll ที่ l>ai+1l > a_{i+1} ซึ่งใช้เวลาได้มากถึง O(n)\mathcal{O}(n)

หากเราเปลี่ยนตาราง dpdp ให้เป็นโครงสร้างของต้น Fenwick Tree ได้ เราจะสามารถหาลำดับลดทุกลำดับที่ลงท้ายด้วยตัวเลขที่มีค่ามากกว่า ai+1a_{i+1} ได้ในเวลา O(logn)\mathcal{O}(\log n) โดยที่เราไม่ต้องพิจารณาทุก ll

ปัญหาต่อมาคือ เราต้องนำจำนวนวิธีของลำดับทั้งหมดใน dpidp_{i} มายัง dpi+1dp_{i+1} ด้วย แต่เราสามารถแก้ปัญหานี้ได้ด้วยการใช้ ตาราง dpdp เดิม ไปเลย

Time Complexity : O(nklogn)\mathcal{O}(nk\log n)
Space Complexity : O(nk)\mathcal{O}(nk)

Code :

#include <bits/stdc++.h>
using namespace std;
const int mod = 999999999;
const int N = 80003, K = 42;
int mul(int a, int b) { return a * 1ll * b % mod; }
int add(int a, int b) {
  int c = a + b;
  if (c >= mod)
    return c - mod;
  return c;
}
int dp[K][N], n;
void update(int k, int l, int val) { // fenwick(dp) update
  for (int i = l; i <= n; i += (i & -i))
    dp[k][i] = add(dp[k][i], val);
}
int query(int k, int l) { // fenwick(dp) query
  int sum = 0;
  for (int i = l; i > 0; i -= (i & -i))
    sum = add(sum, dp[k][i]);
  return sum;
}
int a[N];
int main() {
  int k;
  scanf("%d %d", &n, &k);
  for (int i = 1; i <= n; ++i)
    scanf("%d", &a[i]);
  // transition
  for (int i = 1; i <= n; ++i) {
    for (int j = k; j >= 2; --j) {
      update(j, a[i], add(query(j - 1, n), mod - query(j - 1, a[i])));
    }
    update(1, a[i], 1);
  }
  printf("%d\n", query(k, n));
  return 0;
}