1. 程式人生 > 其它 >資訊學奧賽一本通【例9.3】求最長不下降序列 題解 動態規劃

資訊學奧賽一本通【例9.3】求最長不下降序列 題解 動態規劃

題目連結:http://ybt.ssoier.cn:8088/problem_show.php?pid=1259

題目大意:求一個序列的最長不下降子序列的長度,並輸出任意一個最長不下降子序列。

解題思路:

定義狀態 \(f_i\) 表示以 \(a_i\) 結尾的最長不下降子序列的長度。

\(f_i = 1 + \min\limits_{j = 1 \to i-1(a_j \le a_i)} f_j\)

\(f_i\) 應該等於 \(1\)\(i-1\) 範圍內的滿足 \(a_j \le a_i\)\(f_j + 1\) 的最大值。

這樣就能夠求出所有的 \(f_i\),則所有 \(f_i\)

的最大值就是最長不下降子序列的長度。

但是求出了長度之後還要輸出任意一個滿足條件的最長上升子序列。

這種情況下從前往後找是不對的,但是可以從後往前找,即再額外開一個狀態 \(p_i\)\(p_i\) 表示以 \(a_i\) 結尾的最長不下降子序列中,\(a_i\) 的前一個數的下標。然後可以 遞迴地倒序輸出這些數,具體見 output() 函式,output(x)會先去遞迴地 output \(p_x\)(即 \(x\) 的前一個位置),然後輸出 \(a_x\)。(請認真理解 output 函式的遞迴解法)

然後這道題就解決了。

注意下面程式中的全域性變數 \(x\) 表示的是滿足 \(f_x \ge f_i(1 \le i \le n)\)

\(f_x\) 是最大的那個),因為我要從 \(x\) 下標開始(呼叫 output 函式)遞迴往前(遞迴是往前的,但是輸出是從前往後輸出的,請注意遞迴函式的寫法)。

示例程式:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 202;
int n, a[maxn], f[maxn], p[maxn], x;
void output(int x)
{
    if (p[x]) output(p[x]);
    printf("%d ", a[x]);
}
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++) scanf("%d", a+i);
    for (int i = 1; i <= n; i ++)
    {
        f[i] = 1;
        for (int j = 1; j < i; j ++)
        {
            if (a[j] <= a[i] && f[j]+1 > f[i])
            {
                f[i] = f[j] + 1;
                p[i] = j;
            }
        }
        if (f[i] > f[x]) x = i;
    }
    printf("max=%d\n", f[x]);
    output(x);
    return 0;
}