经典的导弹拦截问题——动态规划和一点形式化证明
这是一道动态规划的经典问题,很多人的博客有写过,但是很多地方只有前半部分,后半部分题目有一些需要认真想想的点我也没见到令我满意的证明,不然我也不会再去写一次来说这个题目的。
问题描述
某国为了防御敌国的导弹袭击,发展出一种导弹 拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的 导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于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 }