题目分为两问
1.一套防御设备能够拦截的最大导弹数
2.需要配备几个防御设备
第一问比较简单,就是一个简单的求最长下降子序列(最长不上升子序列)
第二问求最少防御设备数,要用最少的下降子序列对数组进行全覆盖,根据贪心+队列的思想,就是求最小队列数
相似:P4447 [AHOI2018初中组]分组_qq12323qweeqwe的博客-CSDN博客
贪心思路:
贪心思想的证明
1.贪心得到的队列数是否==最优解
2.证明得到的队列==最优解队列
1.证明队列数
如图,a[i]=4,能够插入的只有i,i+1,贪心解插入的是当前所有可以插入的队列中,长度最小的队列,即q[i],而最优解插入的是q[i+1]
这种最优决策使得当前局面差于贪心决策(会导致每个队列长度不会很平均),所以贪心解<=最优解
由于贪心解>=最优解,可以证明贪心解==最优解
2.证明队列(调整法)
证明队列数一样之后,发现每个队列互不相同
对于所有两个队列不同的某个元素,可以进行交换,进行有限次交换后,可以把贪心策略调整为最优解的策略
如果是一串序列不一致,那么也可以直接交换一串,获得最优解
如果长度不一,可以以少数交换多数,由于每次交换都不会使得两个序列的总长度增加
于是,由该决策包容性,得出最优解可以是贪心解。
dp+dp
由上述问题2的分析,只有不能插入时(a[i]大于所有子序列的结尾)才会增加队数,其实就是最长上升子序列
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1e3+10;
int a[N];
int dp[N];
int len[N];
int q[N];
int cnt;
int main()
{
int n=0;
while(cin>>a[n])
n++;
int res=0;
for(int i=0;i<n;i++)
{
dp[i]=1;
for(int j=0;j<i;j++)
if(a[i]<=a[j])
dp[i]=max(dp[i],dp[j]+1);
res=max(res,dp[i]);
}
cout<<res<<'\n';
int res2=0;
for(int i=0;i<n;i++)
{
dp[i]=1;
for(int j=0;j<i;j++)
if(a[i]>a[j])
dp[i]=max(dp[i],dp[j]+1);
res2=max(res2,dp[i]);
}
cout<<res2;
}
贪心+二分 (O(nlogn))
贪心的做法有两种
1.求队列数时贪心
2.已经队列数==最长上升子序列,对最长上升子序列优化
1.求队列数时优化
求队列数,即求最少需要配备几个防御设备,上面已经证明了
这里要注意的是,求最小组数,他每一组并不一定能够得到最长上升子序列,每一组可能是平均的。
关于求最少组数的时候
我们每一个组的内部都是一个最长不上升子序列(下降)
每次插入都会导致qq中某个组的值减少(存储最后一个元素)
那么如图我们应该插入第一个可以插入的元素,才可以保持单调性
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e3 + 10;
int a[N];
int len[N];
int qq[N]; //存储第一问答案,最长不上升子序列
int q[N]; //存储第二问答案,最长上升子序列
int cnt;
int Binary_search(int x)
{//找到所有可以插入的队列中长度最小的队列
//长度最小的队列==第一个>=a[i]的队列
int l = 0, r = cnt - 1;
while (l < r)
{
int mid = l + r >> 1;
if (qq[mid] > x)
r = mid;
else if (qq[mid] == x)
r = mid;
else
l = mid + 1;
}
return qq[l] >= x ? l : -1;
}
int main()
{
int n = 0;
while (cin >> a[n])
n++;
int res = 0;
q[0] =2e9;
//找到最长不上升子序列
for (int i = 0;i < n;i++)
{
//找到最后一个>=a[i]的数字
int l = 0, r = res;
while (l < r)
{
int mid = l + r+1 >> 1;
if (q[mid] >= a[i])
l = mid;
else
r = mid - 1;
}
res = max(res, l + 1);
q[l + 1] = a[i];
}
cout << res << endl;
//求组数
for (int i = 0;i < n;i++)
{
//求最小组数其实就是模拟分组的过程,这样子得出来的组数一定是最优解
//但是不能保证得到最长不上升子序列
int j = Binary_search(a[i]);
if (!cnt || j == -1)
{
qq[cnt] = a[i];
len[cnt]++;
cnt++;
}
else
qq[j] = a[i], len[j]++;
}
cout << cnt;
}
2.分别优化最长不上升子序列和最长上升子序列
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e3 + 10;
int a[N];
int len[N];
int qq[N]; //存储第一问答案,最长不上升子序列
int q[N]; //存储第二问答案,最长上升子序列
int cnt;
int main()
{
int n = 0;
while (cin >> a[n])
n++;
int res = 0;
q[0] =2e9;
//找到最长不上升子序列
for (int i = 0;i < n;i++)
{
//找到最后一个>=a[i]的数字
int l = 0, r = res;
while (l < r)
{
int mid = l + r+1 >> 1;
if (q[mid] >= a[i])
l = mid;
else
r = mid - 1;
}
res = max(res, l + 1);
q[l + 1] = a[i];
}
cout << res << endl;
res = 0;
qq[0] = -2e9;
for (int i = 0;i < n;i++)
{
//找到最后一个<a[i]的数字
int l = 0, r = res;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (qq[mid] > a[i])
r = mid - 1;
else if (qq[mid] == a[i])
r = mid - 1;
else
l = mid;
}
res = max(res, l + 1);
qq[l + 1] = a[i];
}
cout << res;
}