連接:
最少攔截系統
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 12863 Accepted Submission(s): 5100
Problem Description
某國爲了防護敵國的導彈襲擊,發展出一種導彈攔截系統.可是這種導彈攔截系統有一個缺陷:雖然它的第一發炮彈可以到達任意的高度,可是之後每一發炮彈都不能超過前一發的高度.某天,雷達捕捉到敵國的導彈來襲.因爲該系統還在試用階段,因此只有一套系統,所以有可能不能攔截全部的導彈.
怎麼辦呢?多搞幾套系統唄!你說說倒蠻容易,成本呢?成本是個大問題啊.因此俺就到這裏來求救了,請幫助計算一下最少須要多少套攔截系統.
Input
輸入若干組數據.每組數據包括:導彈總個數(正整數),導彈依此飛來的高度(雷達給出的高度數據是不大於30000的正整數,用空格分隔)
Output
對應每組數據輸出攔截全部導彈最少要配備多少套這種導彈攔截系統.
Sample Input
8 389 207 155 300 299 170 158 65
Sample Output
Source
Recommend
JGShining
算法:貪心 || Dp 【本質同樣】
思路:
算是很簡單的一道題目了,可是從這裏 學會了所謂的 LIS 優化,因此想好好總結下了
開始是用貪心作的了,複雜度 O(n^2)最容易理解的一種寫法了。
複雜度 O(n^2)高了點,仍是比較好理解的了。
/*
最長上升子序列(一維形式DP)
opt[i]=max(opt[j])+1, opt[i]) (0<=j<i 且num[j]<=num[i]) {最長非降低序列}
opt[i]=max(opt[j])+1, opt[i]) (0<=j<i 且num[j]>num[i]) {最長降低序列}
該算法複雜度爲O(n^2)
*/
可是這樣就還不如貪心了效率。後來又去找了下 LIS 的資料:
發現 LIS 的 O(nlogn)的算法其實和貪心的本質很像,下面就以這一題仔細總結下。
樣例中的數據是:
389 207 155 300 299 170 158 65
那麼採起貪心的思路:
以數組 h[] 記錄攔截系統當前的攔截高度,先初始化爲最大值 INF = 30000+10,
表示每個新攔截系統都能攔截全部的導彈,而後遇到一個導彈就往前找看是否有已經使用了的系統能攔截,若是有,直接用;不然從新弄一個系統。最後再看用了幾個系統就行了。
第一個導彈 389 < h[1] ( h[1] = INF)被第一個系統攔截 h[1] = 389
第二個導彈 207 < h[1] 被第一個系統攔截 h[1] = 207
第三個導彈 155 < h[1] h[1] = 155
第四個導彈 300 > h[1] , 300 < h[2] ( h[2] = INF ) 因此新開發一個系統攔截第四個導彈, h[2] = 300
第五個導彈 299 > h[1] , 299 < h[2] 被第二個系統攔截 h[2] = 299
第六個導彈 170 > h[1] , 170 < h[2] h[2] = 170
第七個導彈 158 > h[1] , 158 < h[2] h[2] = 158
第八個導彈 65 < h[1] 被第一個系統攔截 h[1] = 65
因此最後使用了兩個系統就攔截了全部的導彈【遍歷 h[]數組從 1到 n 看有幾個 != INF 就說明使用了】
導彈高度:389 207 155 300 299 170 158 65
使用的攔截系統: 1 1 1 2 2 2 2 1
下面說一下DP的 O(n^n)的思路:
dp[i] 記錄的是攔截第 i 個導彈的系統的編號 index
先初始化全部的導彈都被第一個系統攔截。dp[i] = 1
dp[i] = max(dp[i], dp[j]+1) 【0<=j<i && high[i] > high[j]】
當遇到第 i 個導彈的時候,看前面已經攔截了的導彈的系統是否可以攔截第 i 個導彈 0 <= j < i
一旦第 i 個導彈比第 j 個導彈要高,那麼依照上面貪心的思路,攔截第 i 個導彈的系統一定比攔截第 j 個導彈的系統大,
最少大一個【若是 (j < k < i) 若是第 k 個導彈沒有比第 i 個低的】
(不知道說清楚了沒有,反正我是這麼理解的了)
那麼最後出的 dp[]對應的值將是上面貪心的思想的 h[]的下標
導彈高度 a[]:389 207 155 300 299 170 158 65
使用的攔截系統 dp[]: 1 1 1 2 2 2 2 1
最後輸出最大的 dp[] 就是所需系統的個數了
最後總結下 Dp 的 O (nlogn)的思路:
其實本質上都同樣了,就像這篇神奇的 LIS 的優化,奇怪的命名方式,看了我大半天才懂
首先第一輪遍歷全部的導彈,是不能改了的這裏消耗 O(n)
而後就是把第二重循環改爲了二分而已 ,二分複雜度 O(logn)
首先咱們要明確的是上面 Dp O(n^2) LIS 思想的第二輪找的是什麼?
首先仍是先定義一個 h[] 數組存儲攔截系統的高度,那麼根據前面貪心的分析:
咱們能夠明確這一點只要是用過了的系統 h[] 那麼它必定是單調遞增的
這個時候 h[] 保存的就是遍歷到第 i 個導彈的時候,當前使用過的系統目前可以攔截導彈的最高值
因此第二輪找的是前面第一個 h[index] >= a[i] ,而後再更新 h[index]= a[i]
永遠維護 h[]是單調遞增的,最後輸出 h[]的使用長度,或是像上面貪心同樣遍歷一遍數出用了幾個 h[] 均可以
那麼再回到咱們的關鍵問題:如何使得第二輪的順序查找 O(n) 優化成 O(logn)
注意到:h[]永遠是單調遞增的,那麼直接寫個二分查找就能夠了。基本上對於這題能夠 0 ms 秒過,
最終發現這樣也是網上傳的 LIS 的最優的解法
若是你想偷懶:那麼二分也不用寫了,直接加個 algorithm 的頭文件了,調用 upper_bound就行了【kuangbin大神教的Orz】
int index = upper_bound(h,h+len+1,a[i])-h; //保證 h[index] 是數組 h 中第一個 >= a[i] 的
下面依次貼代碼,看不懂的輸出中間變量就行了。
code:
貪心:
D |
Accepted |
236 KB |
15 ms |
C++ |
746 B |
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn = 1000+10;
const int INF = 30000+10; //導彈高度不會超過 30000
int a[maxn]; //存導彈的高度
int h[maxn]; // h[i] 表示第 i 個導彈系統攔截的最低高度
int main()
{
int n;
while(scanf("%d", &n) != EOF)
{
for(int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
h[i] = INF; //初始化保證每個攔截系統都能攔截全部的導彈
}
for(int i = 0; i < n; i++)
{
for(int j = 0; j <= i; j++) //往前找開發了的導彈系統,看是否能攔截當前導彈, 最壞的結果是每一個導彈都須要一個新的導彈系統來攔截,因此遍歷到 i
{
if(h[j] >= a[i]) //一旦找到符合條件的攔截系統
{
h[j] = a[i]; // 第 j 個攔截系統攔截了第 i 個導彈 , 更新它的目前能夠攔截的導彈的高度
break; //第 i 個導彈已經攔截,跳出裏面那層循環
}
}
}
int tot = 0;
for(int i = 0; i < n; i++) //計算總共用了幾個導彈系統
if(h[i] != INF) //若是第 i 個導彈系統的高度不等於初始值說明它用過
tot++;
printf("%d\n", tot);
}
return 0;
}
DP之 O(n^n)
D |
Accepted |
236 KB |
15 ms |
C++ |
792 B |
/*****************************************************
dp[i] = max(dp[i], dp[j]+1) 【0<=j<i, a[i] > a[j]】
若是當前導彈 i 的高度 > 前面的導彈 j 的高度,
那麼攔截當前導彈 i 的系統,必定是攔截 j 的後面的系統
******************************************************/
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn = 1000+10;
const int INF = 30000+10;
int a[maxn]; //存導彈的高度
int dp[maxn]; //d[i] 表示第 i 個導彈是被第 dp[i] 個攔截系統攔截的
int main()
{
int n;
while(scanf("%d", &n) != EOF)
{
for(int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
dp[i] = 1;
}
for(int i = 0; i < n; i++)
{
for(int j = 0; j < i; j++)
if(a[i] > a[j])
dp[i] = max(dp[i], dp[j]+1);
}
int ans = 0;
for(int i = 0; i < n; i++)
ans = max(ans, dp[i]);
printf("%d\n", ans);
}
return 0;
}
Dp之 O(nlogn) 二分查找:
D |
Accepted |
236 KB |
0 ms |
C++ |
959 B |
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn = 1000+10;
int a[maxn]; //導彈高度
int h[maxn]; // h[i] 表示第 i 個系統目前攔截的高度
int find(int h[], int len, int ha) //返回 index , 數組h[] 中, 第一個h[index] >= ha
{
int left = 0;
int right = len;
while(left <= right)
{
int mid = (left+right) / 2;
if(ha > h[mid]) left = mid+1;
else if(ha < h[mid]) right = mid-1;
else return mid;
}
return left;
}
int main()
{
int n;
while(scanf("%d", &n) != EOF)
{
for(int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
}
h[0] = -1;
h[1] = a[0];
int len = 1;
for(int i = 1; i < n; i++)
{
int index = find(h,len, a[i]);
h[index] = a[i];
//printf("test : h[%d] = %d\n", index, h[index]);
if(index > len)
len = index;
}
printf("%d\n", len);
}
return 0;
}
Dp O (nlogn)之直接調用upper查找
D |
Accepted |
236 KB |
0 ms |
C++ |
814 B |
2013-08-05 11:34:41 |
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn = 1000+10;
const int INF = 30000+10;
int a[maxn]; //導彈高度
int h[maxn]; // h[i] 表示當前第 i 個系統攔截的高度
int main()
{
int n;
while(scanf("%d", &n) != EOF)
{
for(int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
// h[i] = INF;
}
h[0] = -1;
h[1] = a[0];
int len = 1;
for(int i = 1; i < n; i++)
{
int index = upper_bound(h,h+len+1,a[i])-h; //保證 h[index] 是數組 h 中第一個 >= a[i] 的
h[index] = a[i];
//printf("test: h[%d] = %d\n", index, h[index]);
if(index > len)
len = index;
}
//for(int i = 0; i <= n; i++) printf("%d ", h[i]); printf("\n");
printf("%d\n", len);
}
return 0;
}