1、题目
原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=1257
2、dp
思路
先说结论:这道题可以用最长上升子序列反求最长不增子序列的最大数目,因为最长上升子序列中的任意两个导弹都不可能会存在同一个系统中,而不在这个序列中的其他导弹都至少能和序列中的一个导弹存在于同一个系统中。
简单证明一下(如有错漏 欢迎指出):
假设这个最长上升子序列为 a1、b1、c1,那么所有在 a 前面的值必定都大于等于 a (因为如果存在的话子序列的长度就不止 3 个),且在 a 前面的这些值所构成的新序列中,新的最长上升子序列必定不会大于 3,假设为 a2、b2、c2,那么类推下去 a2前面所有值构成的新序列中,新的最长上升子序列也同样不会大于 3,循环往复到 an 前面不足三个的时候,我们会发现,都不足三个值了,再怎么不济,都能接在an、bn、cn 前面,此时可以证明, an 前面并不需要增加新的系统。
同理,在 c 1后面的值也都一定小于等于 c1,和上面一样类推下去,cn 后面,不需要增加新的系统的数,因为都一定可以接在 an、bn、cn 后面。
最后就只剩下夹在 a1、b1、c1 (设为64、65、78)之间的数了。
先看 a1 和 b1 之间夹的数,对此我们可以分类讨论:
①首先夹的这些数的值不可能在 a1 ~ b1 之间,否则最长上升子序列长度不止 3;
②假如夹的数比 a1 小,那么把它们抽离出来,看成一个新序列,这个新序列的最长上升子序列不可能大于 1,不然它们可以代替 a1 从而增加最终最长上升子序列的长度。
打个比方,不可能是 64、7、15、65、78,不然完全可以是7、15、65、78 从而使得最终长度为 4。
换句话数,夹在其中的数单调不增,所以必然可以接在 a1 后面,不用增加新的系统。
③ 假如夹的数比 b1 大,和上面一样可以推出能接在 b1 前面或者可能在c1前面,不用增加新的系统。
④假如夹的数等于 a1 或者等于 b1,那直接接在 a1 后面 b1前面就好啦,不用增加新的系统。
同理,b1 和 c1 之间夹的数也可以推出不需要增加系统。
综上,一个序列中,最长上升子序列长度 == 最长不增子序列的最大数目
代码
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <vector>
#include <map>
#include <set>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <climits>
using namespace std;
#define getlen(array) {return (sizeof(array) / sizeof(array[0]));}
#define ll long long
#define MEM(x, y) memset(x, y, sizeof x)
#define rin int n; scanf("%d", &n)
#define rln ll n; scanf("%lld", &n)
#define rit int t; scanf("%d", &t)
#define sc scanf
#define pr printf
const int INF = 0x3f3f3f3f;
const int N = 10000;
//(val & 1) == 0偶, == 1奇。
//dp[i]:以第 i 个值为终点的最长上升子序列的长度
int nums[1010], dp[1010];
int main() {
//freopen("D:\\in.txt", "r", stdin);
//freopen("D:\\out.txt", "w", stdout);
int n;
while (sc("%d", &n) != -1) {
MEM(dp, 0);
for (int i = 1; i <= n; ++ i) sc("%d", &nums[i]);
int ans = 0;
for (int i = 1; i <= n; ++ i) {
dp[i] = 1;
for (int j = 1; j < i; ++ j) {
if (nums[j] < nums[i]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
ans = max(ans, dp[i]);
}
pr("%d\n", ans);
}
return 0;
}
3、贪心 - 优先队列
思路
对于 155、389、207、300、170、64、65、7、14、78 这个例子,我们可以构建出历程如下的 3 个系统:
155、64、7
389、207、170、14、
300、78
在一开始构建出 155 的时候,389不能接在他后面,所以开了第二个系统,207只能接在 389 后面,遇到 300 又开了一个新的,依次类推。
需要注意的是,64 虽然看起来 155 和 170 后面都能接,但是为了以防后面出现一个在156 到 170 的数导致可能会增加新的不必要的系统,所以应该把 64 接在所有能接的且末尾最小的系统后面。
代码实现方面,可以开一个小根堆,堆内元素为每个系统当前末尾的那个值。
代码
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <vector>
#include <map>
#include <set>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <climits>
using namespace std;
#define getlen(array) {return (sizeof(array) / sizeof(array[0]));}
#define ll long long
#define MEM(x, y) memset(x, y, sizeof x)
#define rin scanf("%d", &n)
#define rln scanf("%lld", &n)
#define rit scanf("%d", &t)
#define sc scanf
#define pr printf
const int INF = 0x3f3f3f3f;
const int N = 10000;
//(val & 1) == 0偶, == 1奇。
//系统
struct sys {
int height;
bool operator < (const sys &a) const {
//重载小于号
return height > a.height;
}
};
int main() {
//freopen("D:\\in.txt", "r", stdin);
//freopen("D:\\out.txt", "w", stdout);
int n;
while (sc("%d", &n) != -1) {
sys s;
sc("%d", &s.height);
priority_queue<sys> pq;
pq.push(s);
for (int i = 1; i < n; ++ i){
sc("%d", &s.height);
stack<sys> stack; //储存堆内不能接下当前导弹的系统
while (!pq.empty() && pq.top().height < s.height) {
sys temp = pq.top();
stack.push(temp);
pq.pop();
}
if (!pq.empty()) {
pq.pop();
}
pq.push(s);
//将拿出的系统放回去
while (!stack.empty()) {
pq.push(stack.top());
stack.pop();
}
}
//计算一共有多少个系统
int cnt = 0;
while (! pq.empty()) {
++ cnt;
pq.pop();
}
pr("%d\n", cnt);
}
return 0;
}