经典的导弹拦截问题——动态规划和一点形式化证明

这是一道动态规划的经典问题,很多人的博客有写过,但是很多地方只有前半部分,后半部分题目有一些需要认真想想的点我也没见到令我满意的证明,不然我也不会再去写一次来说这个题目的。

问题描述
  某国为了防御敌国的导弹袭击,发展出一种导弹  拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的  导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

  输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
  一行,为导弹依次飞来的高度
输出格式
  两行,分别是最多能拦截的导弹数与要拦截所有导弹最少要配备的系统数
样例输入
389  207  155  300  299  170  158  65
样例输出
6
2

————————PART I————————

求数组的非增序列,动态规划来做。先斟酌一个选择,子结构的划分依据<采用当前点,不采用当前点>的背包思想,还是依据<必有当前点+后面每种可以的序列>?很容易明白第二种方法是好的,即是结果不是简单读d[0],也只需要一趟遍历找最大就可以得到结果。第一种其实没什么吸引力。

首次得到最长序列长度后打印结果这些都OK。

————————PART II————————

之后需要多次DP,并用结果来削减数组元素直至空,我用的是vector,所以获得要删除的元素位置后就可以简单的erase了。在动态规划时我维护一个parent数组记录每条路径,所以只要找到入口也就好办了。

但是一直有一个疑问,当有两条长度相等的序列可供erase时,是不是会对下一趟遍历产生影响?这里需要证明一下才能得到算法的正确性。

当前数组为A=<X1,X2,...,Xn>,DP获得两条路径A1=<Xa,...>,A2=<Xb,...>长度相同,A1和A2在最大区间[i, j]内元素数相同且没有相同元素(*)。假设之后对A-A1某一次动态规划获得的子序列长度小于来自A-A2的,所以这某一次DP后的序列中,(A-A1∩[i, j])∩(A-A2∩[i, j])=Ø,且Size(A-A1∩[i, j])<Size(A-A2∩[i, j]),那么在起始序列A中,[i, j]区间内,Size(A1∩[i, j])<Size(A2∩[i, j]),与假设*不符,所以之后某一次DP中,不会产生有差异的结果。所以算法不区分最长DP结果(们)的路径是安全的。

 

 1 #include <vector>
 2 #include <iostream>
 3 using namespace std;
 4 
 5 int dp(vector<int> &v)
 6 {
 7     int n = v.size();
 8     vector<int> d(n, 1);
 9     vector<int> p(n, -1);    //parent
10     for (int i = n - 2; i >= 0; --i)
11     {
12         for (int j = i + 1; j < n; ++j)
13         {
14             //不分开讨论要不要采取这个数必须采取
15             if (v[i] >= v[j] && d[i] < d[j] + 1)
16             {
17                 d[i] = d[j] + 1;
18                 p[i] = j;
19             }
20         }
21     }
22     int mx = -1;
23     int pos = 0;
24     for (int i = pos; i < n; ++i)
25     {
26         if (d[i] > mx)
27         {
28             mx = d[i];
29             pos = i;
30         }
31     }
32     //整理数组
33     vector<int> del;
34     int i = pos;
35     while (p[i] != -1)
36     {
37         del.insert(del.begin(), i);
38         i = p[i];
39     }
40     del.insert(del.begin(), i);
41     for (i = 0; i < del.size(); ++i)
42     {
43         v.erase(v.begin() + del[i]);
44     }
45     return mx;
46 }
47 int main()
48 {
49     vector<int> v;
50     int h;
51     while (cin >> h) v.push_back(h);
52     int i = 1;
53     for (;; ++i)
54     {
55         int ans = dp(v);
56         if (i == 1) cout << ans << endl;
57         if (v.size() == 0) break;
58     }
59     cout << i << endl;
60 //    system("pause");
61     return 0;
62 }

 

转载于:https://www.cnblogs.com/jily/p/6644934.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值