這是一道很經典的動態規劃問題,對於我這種還沒系統學算法的人,還是需要多加練習,有很多人寫過這題的解題報告,我還是決定自己在寫一篇,幫助自己加深理解,感覺對DP沒有什麼感覺,拿到題目思路有點難拓開,怎麼找到動態轉移方程,這對我來說是個問題。
題目鏈接:http://www.rqnoj.cn/Problem_217.html
RQNOJ據說是個高中NOIP選手創的OJ,被廣大高中NOIP競賽選手使用,上面試題都是中文,可以按算法分類搜索題目, 提交程序後有分數,並不是單純WA,或者AC,測試點過了給分,沒過,會給測試數據及應該有的正確答案,這點比較好,練算法可以嘗試這個OJ.
想了好久,還在別人提示之下才找到了動態轉方程:
我們考慮以a[k]爲最後一位的最長不增子序列,作爲一種狀態,那麼總共有n個(最後一個爲a[n-1]), 對於n+1時,我們找到末尾比a[n]小的,最長的不增子序列即可,計算一次即可得到最長子序列的值,即第一個詢問的對象,對於第二的問題,我們需要再考慮一下,我們希望在原來序列上把已經找到的最長子序列去掉,再繼續尋找最長的序列,但是有時會遇到一點問題,當最長子序列可以有多種選擇,怎麼辦?例如有如下情況:
8
1 16 3 6 18 9 14 12
第一步找最長子序列:可以是16 14 12, 或者18 14 12
首先這種糾結的問題只會出現在子序列的首位上, (自己考慮爲什麼),這時候應該去掉這兩者中的那個序列呢?
可以知道首數字後面的總比前面的大,(18>16),(例如18改爲15)不然我們可以取前面的數做第一位再加上後面的所有子序列,得到一個更長的子序列,(16 15 14 12)
因此我們知道後面的首數字更大,
這時候我們應該取小的那個首數字,16,因爲如果取18, 16留下,18後面可能有不能和16組成不增子序列的數字如16,但是如果保留18,而取走16不會有這個問題,18之後的如果能插隊到某個子序列中,現在仍然可以,而介於16 18之間的數一定是比18小的(否則不會有以上討論,後者子序列可以長度加以1變更長了)所以他們本來不能插隊到某個18開頭的子序列中,現在仍然不能.所以我們得出取前面的能更有利於下一步取最長子序列,所以取最前面的那個;
代碼如下:
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
using namespace std;
struct re
{
int len, ak, res[1000];
};
int count(int *a, re *b, int n, int *m)
{
int max = 1, maxi = 1, i, j, k;
b[0].ak = a[0];
b[0].len = 1;
b[0].res[0] = a[0];
for (i = 1; i < n; ++i)
{
b[i].ak = a[i];
b[i].len = 1;
b[i].res[0] = a[i];
for (j = 0; j < i; ++j)
{
if (b[j].ak >= b[i].ak)
{
if (b[i].len < b[j].len + 1)
{
b[i].len = b[j].len + 1;
for (k = 0; k < b[j].len; ++k)
b[i].res[k] = b[j].res[k];
b[i].res[b[j].len] = a[i];
}
}
}
}
for (i = 0; i < n; ++i)
{
if (b[i].len > max)
{
max = b[i].len;
maxi = i;
}
}
*m = maxi;
return max;
}
int main()
{
int i, j, k, n, *a = NULL, *c = NULL, max, maxi, num;
re *b = NULL;
while (cin >> n)
{
i = 0;
max = -1;
maxi = -1;
num = 1;
a = (int *) malloc (n * sizeof(int));
c = (int *) malloc (n * sizeof(int));
b = (re *) malloc (n * sizeof(re));
memset(b, 0, sizeof(b[0]) * n);
while (i < n)
cin >> a[i++];
max = count(a, b, n, &maxi);
cout << max << ' ';
while (max != n)
{
for (i = 0, j = 0, k = 0; i < n; ++i)
{
if (a[i] == b[maxi].res[j])
j++;
else
c[k++] = a[i];
}
n = n - max;
for (i = 0; i < n; ++i)
a[i] = c[i];
memset(b, 0, sizeof(b[0]) * n);
max = count(a, b, n, &maxi);
num++;
}
cout << num << endl;
}
return 0;
}
一些說明:
不斷去掉最長子序列,繼續在剩下的序列中找最長子序列,即可解決第二問,有類似貪心的思想.
結構體b[i]保存了a[0]到a[i]的最長子序列的長度len, 最長子序列最後一個數字ak, 以及這個序列res[1000];