NOIP1999 攔截導彈

這是一道很經典的動態規劃問題,對於我這種還沒系統學算法的人,還是需要多加練習,有很多人寫過這題的解題報告,我還是決定自己在寫一篇,幫助自己加深理解,感覺對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];

 

 

發佈了37 篇原創文章 · 獲贊 6 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章