ybt 1260 【例9.4】拦截导弹(Noip1999)
洛谷 P1020 [NOIP1999 普及组] 导弹拦截
本题为完整问题,拆分后的问题:
第1个问:ybt 1289:拦截导弹
第2个问:ybt 1322:【例6.4】拦截导弹问题(Noip1999)
该解法两问的复杂度均为 O ( n 2 ) O(n^2) O(n2),该解法可以通过【ybt 1260】,提交【洛谷P1020】只能得100分。
不上升子序列:序列中后面的元素小于等于前面的元素
状态定义:dp[i]
表示以i为结尾的最长不上升子序列的长度。
状态转移方程:
分割集合:以第i元素为结尾的不上升子序列构成的集合。
a[j]>=a[i]
,则以第j元素为结尾的不上升子序列加上第i元素,形成新的不上升子序列。不上升子序列的长度dp[i] = dp[j]+1
。dp[i] = 1
。最后求dp数组的最大值,即为最长不上升子序列的长度。
设 h h h表示当前系统可以拦截的最高的高度。
贪心选择:高度小于等于 h h h的导弹中高度最高的导弹。
如果当前系统无法再拦截导弹,而且存在未被拦截的导弹,那么增加一个拦截系统,将 h h h设为无穷大,继续进行贪心选择。
本题贪心选择性质的证明见ybt 1322:【例6.4】拦截导弹问题(Noip1999)
将导弹高度记录在数组a中,设vis数组,表示第i个导弹是否已经被拦截。设h为无穷大。顺序遍历数组a,遇到小于等于h的数字,则拦截该导弹,记录拦截导弹的个数。
遍历结束后,如果拦截到的导弹数量不足n,那么将h设为无穷大,再次遍历该数组。重复上述过程,直到拦截的数量等于n。输出遍历的次数。
该解法提交【洛谷P1020】只能得100分。
Dilworth定理:见偏序关系&Dilworth定理
该定理在本题中,通俗地说就是:
原序列可以划分成的不上升子序列的最小数量为最长上升子序列的长度。(两个黑体字只要满足是相反的描述,该定理即成立。)
证明如下:
获取原序列不上升子序列的最小数量的方法,即为上面解法1中第2问中的贪心方法:每次遍历原序列,只要遍历到的数字小于等于构造中的不上升子序列的最后一个数字,那么把它加到不上升子序列的末尾,将其在原序列删去。反复遍历原序列,每趟遍历得到一个不上升子序列,直到原序列为空,得到的不上升子序列的数量即为可以获得的不上升子序列的最小数量。该方法已经得到了证明,是正确的。
记不上升子序列的最小数量为 k k k,最长上升子序列的长度为 r r r。
反证法:假设在第i-1个不上升子序列中,找不到在原序列中在 e i e_i ei前的且比 e i e_i ei小的元素:
(第i-1个不上升子序列中,一定存在在原序列中排在 e i e_i ei前的元素,否则在构造第i-1个不上升子序列时,就该选择 e i e_i ei。)
那么在第i-1个不上升子序列中,在原序列中在 e i e_i ei前的元素都大于等于 e i e_i ei,那么在构造第i-1个不上升子序列时,就应该把 e i e_i ei接在这些元素的后面,加入第i-1个不上升子序列。而不是把 e i e_i ei留给第i个不上升子序列。
假设不成立。
根据上述方法,
先在第k不上升子序列选出 e k e_k ek
在第k-1不上升子序列中选出 e k − 1 e_{k-1} ek−1,满足 e k − 1 < e k e_{k-1} < e_k ek−1<ek
在第k-2不上升子序列中选出 e k − 2 e_{k-2} ek−2,满足 e k − 2 < e k − 1 e_{k-2} < e_{k-1} ek−2<ek−1
…
最终一定能够取出一个数字序列 e 1 , e 2 , . . . , e k e_1,e_2,...,e_k e1,e2,...,ek,满足 e 1 < e 2 < . . . < e k e_1
第一问自然是求最长不上升子序列的长度。
第二问:各导弹高度构成一个数字序列,每个拦截系统拦截的导弹的高度为原序列的不升子序列。
该题可以抽象为:求一个数字序列的不升子序列的最少个数。根据 Dilworth定理,即为求该数字序列最长上升子序列的长度。
基本动规方法求最长上升或不上升子序列的复杂度为 O ( n 2 ) O(n^2) O(n2),而贪心方法求最长上升或不上升子序列的复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
具体做法见ybt 1281:最长上升子序列,解法2。
求最长不上升子序列时,要尽量增大d[i]
,要查找的是d中小于a[i]
的值的最小下标。
求最长上升子序列时,要尽量减小d[i]
,查找的是d中大于等于a[i]
的值的最小下标。
该解法可以通过【ybt 1260】,提交【洛谷P1020】只能得100分。
#include
using namespace std;
#define N 100005
#define INF 0x3f3f3f3f
int a[N], dp[N];
bool vis[N];//vis[i]:第i导弹是否已选择
int h, n, mxlen, ct, ans;
int main()
{
while(cin >> h)
a[++n] = h;//a[i]:第i个数字
for(int i = 1; i <= n; ++i)
{
dp[i] = 1;//dp[i]:以i为结尾的最长不上升子序列的长度
for(int j = 1; j <= i - 1; ++j)
{
if(a[i] <= a[j])
dp[i] = max(dp[i], dp[j] + 1);
}
mxlen = max(mxlen, dp[i]);
}
cout << mxlen << endl;
while(ct < n)
{
h = INF;//可拦截的高度
for(int i = 1; i <= n; ++i)
{
if(vis[i] == false && h >= a[i])//如果导弹i没被拦截且可以被拦截
{
h = a[i];//可拦截高度降低
vis[i] = true;
ct++;
}
}
ans++;//系统套数加1
}
cout << ans;
return 0;
}
#include
using namespace std;
#define N 100005
int a[N], d[N], len, h, n;
int main()
{
while(cin >> h)
a[++n] = h;//a[i]:第i个数字
d[++len] = a[1];//求最长不上升子序列长度。此时d[i]:长为i的最长不升子序列的末尾元素的最大值
for(int i = 2; i <= n; ++i)
{
if(a[i] <= d[len])
d[++len] = a[i];
else
{
int l = 1, r = len, m;
while(l < r)//降序序列d中找小于a[i]的值的最小下标
{
m = (l+r)/2;
if(d[m] < a[i])
r = m;
else
l = m+1;
}
d[l] = a[i];
}
}
cout << len << endl;
len = 0;//求最长上升子序列长度
d[++len] = a[1];//此时d[i]:长为i的最长上升子序列的末尾元素的最小值
for(int i = 2; i <= n; ++i)
{
if(a[i] > d[len])
d[++len] = a[i];
else
{
int l = 1, r = len, m;
while(l < r)//升序序列d中找大于等于a[i]的值的最小下标
{
m = (l+r)/2;
if(d[m] >= a[i])
r = m;
else
l = m+1;
}
d[l] = a[i];
}
}
cout << len << endl;
return 0;
}
#include
using namespace std;
#define N 100005
int a[N], d[N], len, h, n, l;
int main()
{
while(cin >> h)
a[++n] = h;//a[i]:第i个数字
d[++len] = a[1];//求最长不上升子序列长度。此时d[i]:长为i的最长不升子序列的末尾元素的最大值
for(int i = 2; i <= n; ++i)
{
if(a[i] <= d[len])
d[++len] = a[i];
else
{//降序序列d中找小于a[i]的值的最小下标
l = upper_bound(d+1, d+1+len, a[i], greater<int>())-d;
d[l] = a[i];
}
}
cout << len << endl;
len = 0;//求最长上升子序列长度
d[++len] = a[1];//此时d[i]:长为i的最长上升子序列的末尾元素的最小值
for(int i = 2; i <= n; ++i)
{
if(a[i] > d[len])
d[++len] = a[i];
else
{//升序序列d中找大于等于a[i]的值的最小下标
l = lower_bound(d+1, d+1+len, a[i])-d;
d[l] = a[i];
}
}
cout << len << endl;
return 0;
}