信息学奥赛一本通 1322:【例6.4】拦截导弹问题(Noip1999)

【题目链接】

ybt 1322:【例6.4】拦截导弹问题(Noip1999)
原题有两个问,本题为第2问

【题目考点】

1. 贪心

【解题思路】

h h h表示当前系统可以拦截的最高的高度。
贪心选择:高度小于等于 h h h的导弹中高度最高的导弹。
如果当前系统无法再拦截导弹,而且存在未被拦截的导弹,那么增加一个拦截系统,将 h h h设为无穷大,继续进行贪心选择。

各导弹高度构成一个数字序列,每个拦截系统拦截的导弹的高度为原序列的不升子序列。该题可以抽象为:求一个数字序列的不升子序列的最少个数
以下讨论中使用抽象后的概念。

1. 贪心选择性质的证明:

贪心选择:如果当前数字小于等于某不升子序列的最后一个数字,那么将其接在该不升子序列的末尾。否则将该数字作为一个新的不升子序列的第一个数字。

该题的解为多个不升数字序列。

  1. 证明:存在最优解包含第一次的贪心选择,也就是说整个序列的第一个数字 g g g是某个数字序列的第一个数字。

    这一点是显然的。g是原序列的第一个数字,原序列的子序列中,不可能在g前面仍然有数字。

  2. 证明:假设前k次都进行贪心选择,存在最优解包含第k+1次的贪心选择 g g g
    1) 如果 g g g是某子序列的第一个数

    假设所有的最优解都不包含第一个数是 g g g的子序列,任选一个最优解,存在g不是第一个数字的不升子序列:列 a 1 , a 2 , . . . , g , . . . , a m a_1, a_2, ...,g,..., a_m a1,a2,...,g,...,am。该序列在第k次选择后应该为 a 1 , a 2 , . . . , a l a_1, a_2, ...,a_l a1,a2,...,al,而后又选择了一些数字后,才选择g,即 a 1 , a 2 , . . . , a l , . . . , g a_1, a_2, ...,a_l,..., g a1,a2,...,al,...,g
    因为这是不升子序列,因此一定有 a l ≥ g a_l \ge g alg
    而g的贪心选择,如果贪心选择决定此时g应该是某子序列的第一个数,就一定是因为当前g大于所有已有不升子序列末尾元素。因此有 g > a l g > a_l g>al
    存在矛盾,假设不成立,命题得证。

    2)如果 g g g不是某子序列的第一个数
    也就是说,存在子序列 a x , a x + 1 , . . . , a k a_x, a_{x+1},... ,a_k ax,ax+1,...,ak a k a_k ak是第k次的贪心选择,前k次的贪心选择已经固定。下一次贪心选择是 g g g,应该与 a k a_k ak在同一序列且在 a k a_k ak的后面。

    假设所有最优解中不存在 a k a_k ak在子序列中的下一个数字是 g g g的情况。
    1.如果 a k a_k ak g g g仍然在同一子序列,那么有: a x , . . . , a k , a k + 1 , . . . , g , . . . , a m a_x,... ,a_k, a_{k+1}, ...,g, ..., a_m ax,...,ak,ak+1,...,g,...,am
    这种情况是不可能出现的。第k次贪心选择后,该序列末尾是 a k a_k ak,下一个数字就是g。g前面的数字都已经加入到各个子序列中了,不可能存在在g之前的未加入子序列的 a k + 1 a_{k+1} ak+1
    2.如果 a k a_k ak g g g不在同一子序列中,
    由于g前面的数字都已经加入了各子序列中,如果g接在某个子序列的末尾,就满足了贪心选择。因此g一定是某个序列中的第一个数字,该序列为: g , a g − 1 , g , a g + 1 , . . . , a g e g, a_{g-1}, g, a_{g+1}, ..., a_{ge} g,ag1,g,ag+1,...,age
    第k次贪心选择 a k a_k ak存在的序列为 a x , a x + 1 , . . . , a k , a k + 1 , . . . , a m a_x, a_{x+1},... ,a_k, a_{k+1}, ..., a_m ax,ax+1,...,ak,ak+1,...,am(也可能不存在 a k + 1 , . . . , a m a_{k+1}, ..., a_m ak+1,...,am),
    由于 g ≤ a k g\le a_k gak,序列 a x , a x + 1 , . . . , a k , g , a g + 1 , . . . , a g e a_x, a_{x+1},... ,a_k, g, a_{g+1}, ..., a_{ge} ax,ax+1,...,ak,g,ag+1,...,age一定也是不升序列。
    如果 a k + 1 , . . . , a m a_{k+1}, ..., a_m ak+1,...,am不存在,则将 g g g所在的序列接在 a k a_k ak的后面,构成序列 a x , a x + 1 , . . . , a k , g , a g + 1 , . . . , a g e a_x, a_{x+1},... ,a_k, g, a_{g+1}, ..., a_{ge} ax,ax+1,...,ak,g,ag+1,...,age,总序列数量减少,仍然是最优解。
    如果存在 a k + 1 , . . . , a m a_{k+1}, ..., a_m ak+1,...,am,那么将序列 g , a g + 1 , . . . , a g e g, a_{g+1}, ..., a_{ge} g,ag+1,...,age a k + 1 , . . . , a m a_{k+1}, ..., a_m ak+1,...,am交换。得到序列 a x , a x + 1 , . . . , a k , g , a g + 1 , . . . , a g e a_x, a_{x+1},... ,a_k, g, a_{g+1}, ..., a_{ge} ax,ax+1,...,ak,g,ag+1,...,age a k + 1 , . . . , a m a_{k+1}, ..., a_m ak+1,...,am,总序列数量不变,仍是最优解。
    无论何总情况,总能构造出满足 a k a_k ak在子序列中下一个数字是 g g g的最优解。与假设相悖,假设不成立,原命题得证。

2. 具体做法

将导弹高度记录在数组a中,设vis数组,表示第i个导弹是否已经被拦截。设h为无穷大。顺序遍历数组a,遇到小于等于h的数字,则拦截该导弹,记录拦截导弹的个数。
遍历结束后,如果拦截到的导弹数量不足n,那么将h设为无穷大,再次遍历该数组。重复上述过程,直到拦截的数量等于n。输出遍历的次数。

【题解代码】

解法1:贪心
  • 写法1:
#include
using namespace std;
#define N 1005
#define INF 0x3f3f3f3f
int main()
{
    int a[N], n = 1, ct = 0, h, ans = 0;//ct:拦截的导弹数量 ans:遍历了几趟 
    bool vis[N] = {};
    while(cin >> a[n])
        n++;
    n--;
    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++;
    }
    cout << ans;
    return 0;
}
  • 写法2:
#include 
using namespace std;
int main()
{
	int a[1005], an = 0, h[1005], hn = 0;//h[i]:第i个拦截系统的拦截高度 
	while(cin >> a[an])
		an++;
	for(int i = 0; i < an; ++i)
	{
		bool flag = false;//是否拦截到导弹 
		for(int j = 0; j < hn; ++j)
		{
			if(h[j] >= a[i])
			{
				h[j] = a[i];
				flag = true;
				break;
			}
		}
		if(flag == false)
			h[hn++] = a[i];
	}
	cout << hn; 
	return 0;
}

你可能感兴趣的:(信息学奥赛一本通题解,c++)