[洛谷P1108] 低價購買
題目描述
給定一個序列,求這個序列的最長嚴格下降子序列的長度和數量,特別的是如果兩個子序列只要每個數都相同,無論原來的位置,都算作同一個子序列。
輸入格式
第一行共一個整數 \(n(1 \leq n \leq 5000)\) 表示序列長度。
第二行一行 \(n\) 個整數,表示序列的每個元素。保證是大小不超過 \(2^{16}\) 的正整數。
輸出格式
輸出共一行兩個整數,分別為最大購買次數和擁有最大購買次數的方案數(資料保證 \(\leq 2^{31}\) )。
題解
水藍題一道。資料範圍允許 \(O(n ^ 2)\) 複雜度做法,故用最基礎的求 \(lis\) 的dp方法即可解決第一問。
問題的關鍵是如何統計出得到 \(lis\) 的方案數。自然地可以想到在轉移狀態的同時記錄一下得到當前狀態的方案數 \(cnt_i\) ,如果找到了更優狀態,就把前一個狀態的方案數繼承過來,如果找到了和當前狀態一樣優的子狀態,就把那個狀態的方案數疊加上去即可。
最後剩下的問題就是重複的方案,實際上這中情況僅限於前一個狀態對應的最後一位 \(a_j\) 相等的情況,不難發現對於兩個狀態相同的 \(j_1, j_2(j_1 < j_2)\) 來說,\(j_1\) 的方案是包括在 \(j_2\) 中的,因此我們只統計較為靠後的狀態的方案數即可,可以透過倒著迴圈和打標記的方法解決。最後統計答案的時候用類似的方法將所有不同的狀態等於 \(ans\) 的不相等的方案數累加即可。
AC程式碼
#include <iostream>
#define int long long
using namespace std;
const int MAXN = 5003;
const int MAXA = 1e5 + 3;
int n, a[MAXN];
int f[MAXN], cnt[MAXN]; // 分別記錄長度和方案數
int vis[MAXA]; // 標記是否出現過相同狀態
signed main() {
cin >> n;
// 輸入與初始化
for (int i = 1; i <= n; i++) {
cin >> a[i];
cnt[i] = f[i] = 1;
}
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j < i; j++) vis[a[j]] = 0; // 重置標記
for (int j = i - 1; j > 0; j--) {
if (a[i] < a[j]) { // 合法的狀態
if (f[j] + 1 > f[i]) { // 更優的狀態轉移
f[i] = f[j] + 1;
// 繼承方案數
vis[a[j]] = f[i];
cnt[i] = cnt[j];
} else if (f[j] + 1 == f[i]) { // 與當前狀態一樣優的狀態
// 累加方案數
if (vis[a[j]] != f[i]) cnt[i] += cnt[j]; // 不是相同的狀態就累加方案數
vis[a[j]] = f[i];
}
}
}
ans = max(ans, f[i]); // 更新答案
}
int anscnt = 0; // 統計方案數
for (int i = 1; i <= n; i++) vis[a[i]] = false; // 初始化標記
for (int i = n; i > 0; i--) {
if (vis[a[i]]) continue; // 如果統計過相同的狀態就跳過
if (f[i] == ans) anscnt += cnt[i], vis[a[i]] = true; // 如果是最優狀態就累加到答案裡
}
// 輸出答案
cout << ans << ' ' << anscnt << '\n';
return 0;
}