某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭,由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹的枚数和导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,每个数据之间有一个空格),计算这套系统最多能拦截多少导弹?
如果要拦截所有导弹最少要配备多少套这种导弹拦截系统?
第一行数字n表示n个导弹(n<=200) 第二行n个数字,表示n个导弹的高度 。
一个整数,表示最多能拦截的导弹数。
一个整数,表示要拦截所有导弹最少要配备的系统数 。
样例输入:(missile.in)
8
389 207 155 300 299 170 158 65
样例输出:(missile.out)
6
2
时间限制:1s
空间限制:256MB
【解析】
题目可以理解为,给你一串数字,让你自选一个开始,但每次选择的数字都不能大于上一次选择的数字,其实第一问就等价于求最长不上升子序列的长度。
我们尝试用DP来解决这道经典问题。
由DP的无后效性可以得知,我们要寻找拦截的那串导弹的截止点。
用f[i]表示拦截到第i个导弹时最多拦截的导弹数,如果第i个导弹的高度小于等于第j个导弹的高度,那么我们可以枚举1~i-1这些导弹,选择f数值大的作为它的后继。
如图:
于是,我们可以得出状态转移方程:
当然,f[i]=1,因为它们自己本身也算一个序列。
最后,对所有的f[i]取最大值(不要问我为什么)
【核心代码】
for(int i=1;i<=n;i++){
f[i]=1; //记录第i个前面的最长不上升子序列的长度
for(int j=1;j<i;j++){
if(h[i]<=h[j])
f[i]=max(f[i],f[j]+1);
}
maxx=max(maxx,f[i]);
}
那么,第二问该怎么做呢?
这时候,我们需要一个十分美妙(変態)的东西,dilworth定理:
*度娘定义:在数学理论中的序理论与组合数学中,Dilworth定理根据序列划分的最小数量的链描述了任何有限偏序集的宽度。其名称取自数学家Robert P. Dilworth。
反链是一种偏序集,其任意两个元素不可比;而链则是一种任意两个元素可比的偏序集。Dilworth定理说明,存在一个反链A与一个将序列划分为链族P的划分,使得划分中链的数量等于集合A的基数。当存在这种情况时,对任何至多能包含来自P中每一个成员一个元素的反链,A一定是此序列中的最大反链。同样地,对于任何最少包含A中的每一个元素的一个链的划分,P也一定是序列可以划分出的最小链族。偏序集的宽度被定义为A与P的共同大小。
另一种Dilworth定理的等价表述是:在有穷偏序集中,任何反链最大元素数目等于任何将集合到链的划分中链的最小数目。一个关于无限偏序集的理论指出,在此种情况下,一个偏序集具有有限的宽度w,当且仅当它可以划分为最少w条链。
归纳性证明
令P为一有限偏序集,理论认为P为空集时显然成立。假设P最少有一个元素,令a为P中的极大值。
根据归纳法,假设存在一整数k,使得偏序集P’:=P \ {a}可以被k个不相交的链 C1,……,Ck覆盖,且最少存在一个大小为k的反链 A0。显然,A0∩Ci≠∅,i=1,2,……,k 。令 xi为Ci的极大值,i=1,2,……,k,Ci为P’中大小为k的反链,令A:={x1,x2,……,xk},Ai为包含xi的大小为k的反链。确定任意不等的索引i,j,那么Ai∩Cj≠∅ 。令y∈Ai∩Ci,根据xj的定义,y≤xj。因此,由xi不大于等于y推断出xi不大于等于xj。通过交换i,j,可以得到xj不大于等于xi。由此得证,A为反链。
现在来讨论P。首先假设,{a}∪{z∈Ci:z≤xi},{a}∪{z∈Ci:z≤xi}。令K为链P\K。那么,通过选择A\{xi},使得P\K不包含大小为k的反链。由于P\K是P\K中大小为k-1的反链,归纳推出P\K可以被k-1个不相交的链覆盖。因此,正如所需要证明的,P可以被k个不相交的链覆盖。其次,如果A∪{a},{a},C1,C2,……,Ck,那么由于a是P的极大值,为P中大小为k+1的反链。现在,P可以被k+1个链 覆盖。到此,定理全部证明结束。
(看懂了吗?ヾ(。ꏿ﹏ꏿ)ノ゙没关系,我也没看懂,如果有哪位大佬看懂的请讲解一下,我好菜啊~好了,下面是正文)
这个定理把第二个问题转化成下面的问题:
在一个序列中,划分成最少的最长不上升子序列的数量等于这个序列的最长上升子序列的长度
所以,我们只要求这个序列最长上升子序列的长度,思路跟第一题差不多,只不过,条件要换成h[i]>h[j]。
【核心代码】
for(int i=1;i<=n;i++){
g[i]=1;
for(int j=1;j<i;j++){
if(h[i]>h[j]) //求最长上升子序列的长度
g[i]=max(g[i],g[j]+1);
}
mixn=max(mixn,g[i]);
}
好了,问题圆满解决!٩(๑❛ᴗ❛๑)۶
#include
using namespace std;
const int L=200+100;
int n;
int h[L];
int a[L];
int f[L],g[L];
int maxx=-11111111;
int mixn=-11111111;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&h[i]);
for(int i=1;i<=n;i++){
f[i]=1; //记录第i个前面的最长不上升子序列的长度
for(int j=1;j<i;j++){
if(h[i]<=h[j])
f[i]=max(f[i],f[j]+1);
}
maxx=max(maxx,f[i]);
}
for(int i=1;i<=n;i++){
g[i]=1;
for(int j=1;j<i;j++){
if(h[i]>h[j]) //求最长上升子序列的长度
g[i]=max(g[i],g[j]+1);
}
mixn=max(mixn,g[i]);
}
printf("%d\n%d\n",maxx,mixn);
return 0;
}
这是DP的入门题,一定要好好掌握!