dp与贪心解LIS问题
1、dp + dp
O(n^2)
第一问显然每套导弹拦截系统拦截导弹高度为不升子序列,求最长的就好了
第二问求导弹拦截系统的个数可以转化为求最长上升子序列长度
证明见: Tian-Xing’s blog orz orz
1、首先我们把这些导弹分为s组(s即为所求答案)
可以看出每一组都是一个不升子序列
2、划分完后我们在组一里找一个原序列里以组一的开头点连续的不升子串的最后一个元素,可以知道在组2中一定有一个大与它的点
(如果组二中没有的话,那么组二中最高的导弹高度必然小于这个点,而其他的高度都小于这个高度而且是递减或相等的,那么没有必要再开一个组二了,矛盾,所以不存在找不到比他大的点的情况)
3、以此类推,对于每一个k组(1<=k<n)都可以找到这样的一些点
所以把这些点连起来,就是一条上升子序列。
4、设最长上升子序列长度为l
所求上升子序列为h
那么h<=l
因为最长上升子序列任意两个不在一组内
(如果在同一个组内,则每个组的数不成为一个不生子序列,矛盾)
所以l==h比较难理解
我们来看组数据
389 207 155 300 299 170 158 65
组一 389 207 155 65 组二 300 299 170 158
步骤一中我们一开始找到的点是1
因为如果找65不好解释,所以我们找原数列里连续的最后一个即155
组二里可以找到300比他大
所以最长上升子序列长度为2==答案
代码
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1005;
int n, a[maxn];
int f[maxn], g[maxn];
int main()
{
while(cin >> a[n]) n ++;
int ans1 = 0;
int ans2 = 0;
for(int i = 0; i < n; i ++)
{
f[i] = 1;
g[i] = 1;
for(int j = 0; j < i; j ++)
{
if(a[i] <= a[j]) f[i] = max(f[i], f[j] + 1);
else g[i] = max(g[i], g[j] + 1);
}
ans1 = max(ans1, f[i]);
ans2 = max(ans2, g[i]);
}
cout << ans1 << endl;
cout << ans2 << endl;
return 0;
}
2、dp + 贪心
O(n^2)
题目的第二问,对于第i号导弹,要么选择末尾导弹高度最小的拦截系统,要么新创一个拦截系统,用一个数字即每套拦截系统此时所拦截的最后一个导弹高度,来表示该系统。
这样就得到了一个数组,数组最终长度就是所需最少拦截系统数目。
int main()
{
while(cin >> a[n]) n ++;
int ans = 0, cnt = 0;
for(int i = 0; i < n; i ++)
{
f[i] = 1;
for(int j = 0; j < i; j ++)
{
if(a[i] <= a[j]) f[i] = max(f[i], f[j] + 1);
}
ans = max(ans, f[i]);
//数组g的每个元素代表一套导弹拦截系统的拦截序列
//g[i]表示此时第i套导弹拦截系统所拦截的最后一个导弹的高度
int p = lower_bound(g, g+cnt, a[i]) - g;
if(p == cnt) g[cnt ++] = a[i]; //a[i]开创一套新拦截系统
else g[p] = a[i]; //a[i]成为第p套拦截系统最后一个导弹高度
}
cout << ans << endl;
cout << cnt << endl;
return 0;
}
3、贪心 + 贪心
O(nlogn)
int main()
{
//cnt表示导弹系统数,len表示一个系统最多能拦截的导弹数
int len = 0, cnt = 0;
int a;
while(cin >> a)
{
//pos1表示以a结尾的最长不升子序列长度
int pos1 = upper_bound(f, f+len, a, greater<int>()) - f;
if(pos1 == len) f[len ++] = a;
else f[pos1] = a;
int pos2 = lower_bound(g, g+cnt, a[i]) - g;
if(pos2 == cnt) g[cnt ++] = a;
else g[pos2] = a;
}
cout << len << endl;
cout << cnt << endl;
return 0;
}
upper_bound
与lower_bound
傻傻分不清楚
cpp reference
若数组升序排列
lower_bound(begin, end, a)
返回数组[begin, end)之间第一个大于或等于a的地址,找不到返回end (可以等于所以low)
upper_bound(begin, end, a)
返回数组[begin, end)之间第一个大于a的地址,找不到返回end (一定大于所以up)
若数组降序排列,可写上比较函数greater<type>()
lower_bound(begin, end, a, greater<int>())
返回数组[begin, end)之间第一个小于或等于a的地址,找不到返回end
upper_bound(begin, end, a, greater<int>())
返回数组[begin, end)之间第一个小于a的地址,找不到返回end
非数值数组的情况可选择手写比较函数,如
bool cmp(node a, node b)
{
if(a.value1 != b.value1) return a.value1 < b.value1;
else return a.value2 < b.value2;
}
### 看了老半天才明白(笨
说一下我的理解
整体思想大概是,求两个序列的LIS
第一个序列就是数组本身(把一个数看成一个元素),第二个序列是不同分组的结尾元素所构成的序列(把分组看成一个元素)
第二个转化直接想很难想,需要挖掘一下,可以画个图方便理解
主要是能够想到, 每个元素要么加入到已有的分组里面,要么新开一个分组
为了使得总共的设备数量最少,所以同时有两种选择的话,一定是优先加入到原来分组后面
能加到分组后面的前提就是,这个分组的最后一个元素大于等于当前元素
所以我们可以把最后一个元素作为一个分组的代表元素
贪心的做法就是基础课的 最长上升子序列Ⅱ 的做法,保证了不同长度下的LIS,结尾元素一定是最小的,这样后面的元素就有更多的余地
可以,说的挺明白的,这里再用样例举下例子:
389 207 155 300 299 170 158 65
比如说我们当前外层循环i指针指向170,内层循环j指针指向155,
此时就会执行 g[i] = max(g[i], g[j] + 1),此时g[i]就会更新为2,也就是两组LDS(longest decresing subsequence)。
这道题目为什么不选择,多做几次dp,每次删掉已经拦截掉的导弹呢?
你怎么知道你删掉的是哪个呢,你的f数组代表的是方案
数
可以解释的再详细一点儿吗?不太理解什么意思。
我也想问这个。。可以记录路径的
因为一次DP中,你找到的路径,确实是本次的最优路径,但不一定是整体最优路径。
因为多次DP,互相之间不关心,只关心自己最优,其实是局部最优解,而不是全局最优解。
举个栗子,某次选择 你可以选择张三,李四,王五,都可以获利30元,但是选择张三,钱六,王五也是30元,你选择哪个呢?你肯定说无所谓!但却有所谓:如果选择了李四的话,那么钱六一会可以和其它剩余的人员组合出10000元,可你却选择了让钱六,后面李四与剩余的人只能组合出50元,你说说是不是你的第一次决策错误了?但由于你是多次决策,彼此无关,所以,你不知道你错了。
我就是这样做的,但只过了4个样例就TLE了
现在理解了,因为dp是用一个数代表一条路径,dfs是需要真的做出来
局部最优解不一定是全局最优解
3、以此类推,对于每一个k组(1<=k<n)都可以找到这样的一些点
所以把这些点连起来,就是一条上升子序列。问一下,第二组比第一组大的点和第三组比第二组大的点怎么判断大小的?为什么一定是上升的
stO Or2
请问第二问的贪心做法中
为什么不是
题目说是不高于,为什么在贪心的时候需要把当前数放到比它大的数后面,而不是大于等于它的数后面
我懂了,想错了
感觉有点像区间合并嘛
二分不行的 因为序列中的所有数都可能小于x
是可以的,设置一个哨兵项即可,找的是比x小的最大的数,这样因为有哨兵项就肯定有比x小的数了,插入的位置就是l+1
为什么是求最少需要配备的拦截系统是求最长上升子序列啊?
上升序列的两个点不可能在同一组中
这个数列不是无序的吗,为什么可以用lower_bound(g, g+cnt, a[i]) - g;啊
你维护的是一个单调上升/下降序列,在你维护的序列中用的
%%% Or2
Orz ans stO
%%%
各位帮我看看,我想a数组下标从1开始,和手写一个二分,但有一个样例过不了
第二问就是最长上升子序列Ⅱ吧?
%%%
妙啊
%%%%%%%%%%%%%%%%%
nb
卧槽,妙啊
%%%