信息学奥赛一本通 1260 【例9.4】拦截导弹(Noip1999) | 洛谷 P1020 [NOIP1999 普及组] 导弹拦截

【题目链接】

ybt 1260 【例9.4】拦截导弹(Noip1999)
洛谷 P1020 [NOIP1999 普及组] 导弹拦截
本题为完整问题,拆分后的问题:
第1个问:ybt 1289:拦截导弹
第2个问:ybt 1322:【例6.4】拦截导弹问题(Noip1999)

【题目考点】

1. 动态规划:线性动规

  • 贪心求最长上升子序列

2. 贪心

【解题思路】

解法1: O ( n 2 ) O(n^2) O(n2)解法

该解法两问的复杂度均为 O ( n 2 ) O(n^2) O(n2),该解法可以通过【ybt 1260】,提交【洛谷P1020】只能得100分。

第1问:动态规划:求最长上升子序列

不上升子序列:序列中后面的元素小于等于前面的元素
状态定义:dp[i]表示以i为结尾的最长不上升子序列的长度。
状态转移方程:
分割集合:以第i元素为结尾的不上升子序列构成的集合。

  • 子集1:对所有满足j < i的j, 如果a[j]>=a[i],则以第j元素为结尾的不上升子序列加上第i元素,形成新的不上升子序列。不上升子序列的长度dp[i] = dp[j]+1
  • 子集2:否则,只有一个第i元素构成不上升子序列。dp[i] = 1
  • 以上取到的各值求最大值

最后求dp数组的最大值,即为最长不上升子序列的长度。

第2问:贪心

h h h表示当前系统可以拦截的最高的高度。
贪心选择:高度小于等于 h h h的导弹中高度最高的导弹。
如果当前系统无法再拦截导弹,而且存在未被拦截的导弹,那么增加一个拦截系统,将 h h h设为无穷大,继续进行贪心选择。
本题贪心选择性质的证明见ybt 1322:【例6.4】拦截导弹问题(Noip1999)
将导弹高度记录在数组a中,设vis数组,表示第i个导弹是否已经被拦截。设h为无穷大。顺序遍历数组a,遇到小于等于h的数字,则拦截该导弹,记录拦截导弹的个数。
遍历结束后,如果拦截到的导弹数量不足n,那么将h设为无穷大,再次遍历该数组。重复上述过程,直到拦截的数量等于n。输出遍历的次数。

解法2: O ( n l o g n ) O(nlogn) O(nlogn)解法

该解法提交【洛谷P1020】只能得100分。

1. Dilworth定理在本题中的应用

Dilworth定理:见偏序关系&Dilworth定理
该定理在本题中,通俗地说就是:
原序列可以划分成的不上升子序列的最小数量为最长上升子序列的长度。(两个黑体字只要满足是相反的描述,该定理即成立。)
证明如下:
获取原序列不上升子序列的最小数量的方法,即为上面解法1中第2问中的贪心方法:每次遍历原序列,只要遍历到的数字小于等于构造中的不上升子序列的最后一个数字,那么把它加到不上升子序列的末尾,将其在原序列删去。反复遍历原序列,每趟遍历得到一个不上升子序列,直到原序列为空,得到的不上升子序列的数量即为可以获得的不上升子序列的最小数量。该方法已经得到了证明,是正确的。

记不上升子序列的最小数量为 k k k,最长上升子序列的长度为 r r r

  • 第一点:证明 k ≤ r k \le r kr
    用上述方法获得的第i个不上升子序列中的某个元素 e i e_i ei,一定能在第i-1个不上升子序列中找到一个元素 e i − 1 e_{i-1} ei1(在原序列中 e i − 1 e_{i-1} ei1 e i e_i ei的前面)使得 e i − 1 < e i e_{i-1} < e_i ei1<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-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} ek1,满足 e k − 1 < e k e_{k-1} < e_k ek1<ek
在第k-2不上升子序列中选出 e k − 2 e_{k-2} ek2,满足 e k − 2 < e k − 1 e_{k-2} < e_{k-1} ek2<ek1

最终一定能够取出一个数字序列 e 1 , e 2 , . . . , e k e_1,e_2,...,e_k e1,e2,...,ek,满足 e 1 < e 2 < . . . < e k e_1e1<e2<...<ek,这个序列是一个上升子序列,长度为k。由于最长上升子序列长度为r,所以有 k ≤ r k \le r kr

  • 第二点:证明 k ≥ r k \ge r kr
    最长上升子序列中的任意两个元素: a i a_i ai a j a_j aj
    由于这是个上升序列,在 i < j ii<j时一定有 a i < a j a_i < a_j ai<aj
    那么这两个元素一定不能存在于同一个不上升子序列中。
    因此最长上升子序列中的每个元素都必须存在于不同的不上升子序列中,不上升子序列的最小数量k最少为r
    因此 k ≥ r k \ge r kr
  • 因此 k = r k = r k=r

2. 题目分析

第一问自然是求最长不上升子序列的长度。
第二问:各导弹高度构成一个数字序列,每个拦截系统拦截的导弹的高度为原序列的不升子序列。
该题可以抽象为:求一个数字序列的不升子序列的最少个数。根据 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]的值的最小下标。

【题解代码】

解法1: O ( n 2 ) O(n^2) O(n2)解法

该解法可以通过【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;
}

解法1: O ( n l o g n ) O(nlogn) O(nlogn)解法

  • 手写二分
#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;
}
  • 使用stl
#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;
}

你可能感兴趣的:(洛谷题解,信息学奥赛一本通题解,NOIP真题解答,贪心算法,动态规划,c++)