这个题目我wa了3发……确实有点坑,我们来细品……
这个题目说,第一发炮弹可以达到任意的高度,只是后面的炮弹不得高于这个高度,问依次来n枚导弹,最少需要多少个拦截系统来拦截这些导弹
我一开始以为,一个拦截系统,没有时效性,第一次的炮弹击落了第一个导弹之后,后面的导弹如果高度低于前一个,那么还可以继续用这个系统,但是如果高于前一个,则需要增加一个系统,那么根据样例:
8
389 207 155 300 299 170 158 65
我们需要两个拦截系统:
第一个系统:389 207 155
第二个系统:300 299 170 158 65
当时心想,这TM也忒简单了吧!
然后五行代码提交……wa的无地自容
构造出一个反例:
8
7 6 5 6 3 2 4 1
如果按照第一个思路,就应该输出3,如下:
第一个系统:7 6 5
第二个系统:6 3 2
第三个系统:4 1
但是,别人告诉我(哎,我又理解错题意了):这样应该输出2
因为导弹高度为4的可以被第一个系统击落,因为4小于5,然后1可以被第二个系统击落,因为1小于2……好坑啊
每次输入一个导弹的高速,我们都应该去扫一遍已知系统的当前高度,然后去寻找合适的一个系统去拦截,如果没有这样的系统,则需要重新增加一个系统。
那么什么系统是合适的系统呢?首先,必然这个系统的当前高度要不低于这个导弹的高度,不然就无法拦截,其次,如果有多个系统满足条件,我们应该怎么选取呢?
再次看这个样例:
8
389 207 155 300 299 170 158 65
系统调度:
第一个系统:
389 207 155
第二个系统:
300 299 170 158
然后 65 这个导弹应该放到哪里去呢?显然这个导弹的高度,可以由系统2或者系统1去拦截,假设使用系统2拦截,则第二个系统的高度变成:65,第一个系统高度是155,如果此时再来一个导弹,高度是157,则无法实施拦截,只能再增加一个系统,但是如果是把65交给第一个系统拦截,则两个系统的高度为:65 158,这样就可以很好的拦截可能出现的:157高度的导弹,所以我们贪心的策略就是:
选择在满足可以拦截的系统中,当前高度最小的那一个,因为要维护最强拦截系统
#include <cstdio> const int maxn = 1e4 + 5; int n, h[maxn], boom; int main(){ while(~scanf("%d", &n)){ int cnt = 1; scanf("%d", &boom); h[cnt] = boom; n--; while(n--){ scanf("%d", &boom); int pos = 0, val = 30005, isVis = 0; for(int i = 1;i <= cnt;i++){ if(boom <= h[i]){ if(val > h[i]){ val = h[i]; pos = i; isVis = 1; } } } if(isVis) h[pos] = boom; else h[++cnt] = boom; } printf("%d\n", cnt); } return 0; }
实际上是在玩最长不增子序列,是LIS的应用,再看那个样例:
8
389 207 155 300 299 170 158 65
首先设置一个标记数组:vis[] = {0};
然后跑一遍最长不增子序列:
序列1:389 300 299 170158 65,然后他们都标记成已经被访问
序列2:207 155
再看看那个坑例子:
8
7 6 5 6 3 2 4 1
序列1:7 6 5 3 2 1
序列2:6 4
但是,它的实现是比较困难的, 因为我们需要跑很多次最长不增子序列,还需要去检查,直到所有数据都被访问(所有导弹都被拦截)
观察样例中的第一个最长不增子序列:
X{389,300,299,170,158,65}
Y{207,155}
我们可以发现:
在Y中必然至少有一个数a比X中某个数大,否则Y中的所有数都比X中的数小,这样的话,Y中这个数a就应该在X中。那么如果顺着看,求最长递增子序列,每个不增子序列都能且只能提供一个元素,于是最长递增子序列的规模即是答案。
原因是:
对于任意两个最长不增子序列的集合,都满足我上述的性质,所以假设有两枚导弹 x,y 来自同一个拦截系统,则 x > y 于是与递增违背
#include <cstdio> const int maxn = 1e4 + 5; int n, h[maxn]; int LIS(){ int ans = 1, dp[maxn]; dp[1] = 1; for(int i = 2;i <= n;i++){ int MaxVal = 0; for(int j = 1;j < i;j++) if(h[j] < h[i] && MaxVal < dp[j]) MaxVal = dp[j]; dp[i] = MaxVal + 1; if(dp[i] > ans) ans = dp[i]; } return ans; } int main(){ while(~scanf("%d", &n)){ for(int i = 1;i <= n;i++) scanf("%d", &h[i]); printf("%d\n", LIS()); } return 0; }分析最长递增子序列算法
如何求一个序列的最长递增子序列?
已知序列:389 207 155 300 299 170 158 65
排好序: 65 155 158 170 207 299 300 389
最长公共子序列:155 170 或 155 158 等等
所以得出结论:
排好序的序列和原序列的最长公共子序列就是最长递增子序列
if a[i] = b[j] dp[i][j] = dp[i - 1][j - 1] + 1; else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
#include <iostream> #include <algorithm> using namespace std; int LCS(int a[], int b[], int n){ int dp[100][100] = {0}, ans = 0; for(int i = 1;i <= n;i++){ for(int j = 1;j <= n;j++){ if(a[i] == b[j]) dp[i][j] = dp[i - 1][j - 1] + 1; else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); if(dp[i][j] > ans) ans = dp[i][j]; } } return ans; } int main(){ int n, a[100] = {0}, b[100] = {0}; cin >> n; for(int i = 1;i <= n;i++){ cin >> a[i]; b[i] = a[i]; } sort(b + 1, b + n + 1); cout << LCS(a, b, n); return 0; }
设状态:dp[i]是以第 i 个元素结尾的最长递增子序列
则状态转移:dp[i] = max(0, dp[j]) + 1; 1 <= j < i && a[j] < a[i];
意思就是,只要前面的序列有比你小的元素,下标为 j ,那么你当前的状态就可以由 dp[j]转移得到,然后遍历所有你之前的状态,取最优子结构作为你的上一个状态去转移即可。
int LIS(){ int ans = 1, dp[maxn]; dp[1] = 1; for(int i = 2;i <= n;i++){ int MaxVal = 0; for(int j = 1;j < i;j++) if(h[j] < h[i] && MaxVal < dp[j]) MaxVal = dp[j]; dp[i] = MaxVal + 1; if(dp[i] > ans) ans = dp[i]; } return ans; }
我们维护一个单调栈,但是与一般单调栈问题处理的方式不一样,我们遇到失去单调性的元素的时候,我们用二分法(因为栈内元素单调)去栈内查找最优的替换策略,什么替换策略是最优的呢?自然是替换在不破坏单调性的情况下,能够替换的最大的数,这个数就是在栈中,比它大的最小的数,这个大家细细品味。因为这样替换之后,整个当前序列的数值下降最大,只有当前数值减少,后面才能加入更多的数。
#include <iostream> using namespace std; const int maxn = 1e5 + 5; int n, a, Stack[maxn], cnt; int main(){ cin >> n; for(int i = 1;i <= n;i++){ cin >> a; if(a > Stack[cnt] || !cnt) Stack[++cnt] = a; else{ int l = 1, r = cnt; while(l <= r){ int mid = (r + l) >> 1; if(a <= Stack[mid]) // 搜索第一个出现的比 a 大的数,也就是比a大的最小的数 r = mid - 1; else l = mid + 1; } Stack[l] = a; // 用 a 替换! } } cout << cnt << endl; return 0; }最后讲一下最长公共子串
子串和子序列有什么区别???
子序列可以不连续,但是子串务必要连续!所以子串的状态转移方程就在子序列的基础上稍微修改即可:
if(a[i] == b[j]) dp[i][j] = dp[i - 1][j - 1] + 1; else dp[i][j] = 0;