LuoGuP1020导弹拦截【单调栈+二分深刻理解】【做题报告】
这是一道水题,但是对我的二分有很大启发,也可以n2DP但是显然单调栈nlogn更佳
题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是\le 50000≤50000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入输出格式
输入格式:
11行,若干个整数(个数\le 100000≤100000)
输出格式:
22行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入输出样例
说明
为了让大家更好地测试n方算法,本题开启spj,n方100分,nlogn200分
每点两问,按问给分
这道题就是求最长不上升和上升子序列;
这是为什么呢,第一问很好理解,第二问为为什么求最长上升就行了呢??
这个可以脑补一下,相当于是一个个的上升序列,然后他们的队头构成了最后的最长上升
然后我就想,为什么不能在每一个队里选出来更多的数组成更长的答案?
这是不成立的,因为这个队列是单调递减,只能一个队里选一个(因为如果选两个就不是上升了),所以一个队里选一个,最后答案还是一样的,并不影响
然后呢,n2是白给的(思路很好懂,暴力贪心)
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 using namespace std; 5 int cnt; 6 int a[100011]; 7 int dp[100011]; 8 int main() 9 { 10 // freopen("testdata.in","r",stdin); 11 while(scanf("%d",&a[++cnt])!=EOF)continue;cnt--; 12 dp[1]=1; 13 for(int i=2;i<=cnt;i++) 14 { 15 int tmp=0; 16 for(int j=1;j<=i-1;j++) 17 { 18 if(a[j]>=a[i])tmp=max(tmp,dp[j]); 19 } 20 dp[i]=max(dp[i],tmp+1); 21 } 22 int ans=-1; 23 for(int i=1;i<=cnt;i++)ans=max(ans,dp[i]); 24 printf("%d\n",ans); 25 memset(dp,0,sizeof(dp)); 26 dp[1]=1; 27 for(int i=2;i<=cnt;i++) 28 { 29 int tmp=0; 30 for(int j=1;j<=i-1;j++) 31 { 32 if(a[j]<a[i])tmp=max(tmp,dp[j]); 33 } 34 dp[i]=max(dp[i],tmp+1); 35 } 36 ans=-1; 37 for(int i=1;i<=cnt;i++)ans=max(ans,dp[i]); 38 printf("%d\n",ans); 39 return 0; 40 } 41 /*1 2 3 4 5*/ 42 /* 43 389 207 155 300 299 170 158 65 44 */
然后便是单调栈的写法(一开始以为是单调队列,但是单调队列是维护需要出队的,用来缓存数组,优化时间复杂度,显然这道题并不用)
单调栈的思路就是维护一个单调的栈,每次寻找最底层比它大(或小)的位置将当前元素插入,最后栈内有多少个元素就是答案
那么插入的过程呢,对,用二分来维护,这样才能保证是每次log
那么我们来看一下这个二分
for(int i=2;i<=cnt;i++) { int l=1,r=top,mid,tt=top+1; while(l<=r) { mid=(l+r)/2; if(stk[mid]>=a[i])l=mid+1; else r=mid-1,tt=mid; } if(tt==top+1)top++; stk[tt]=a[i]; } for(int i=2;i<=cnt;i++) { int l=1,r=top,mid,tt=top+1; while(l<=r) { mid=(l+r)/2; if(stk[mid]<a[i])l=mid+1; else r=mid-1,tt=mid; } if(tt==top+1)top++; stk[tt]=a[i]; }
这个tt=mid,也就是记录ans的地方到底应该加在哪里呢,之前是背模板,以为就是在r那里,但是不是这样的,
应该是哪个符合你想要的在哪里记录ans,也就是有一个满足就记录一下
然后就没什么问题了,注意对top指针的维护就好;
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 using namespace std; 5 int cnt,head,tail,ans=-1,top=1; 6 int a[100011]; 7 int stk[100011]; 8 int main() 9 { 10 while(scanf("%d",&a[++cnt])!=EOF)continue;cnt--; 11 stk[1]=a[1]; 12 for(int i=2;i<=cnt;i++) 13 { 14 int l=1,r=top,mid,tt=top+1; 15 while(l<=r) 16 { 17 mid=(l+r)/2; 18 if(stk[mid]>=a[i])l=mid+1; 19 else r=mid-1,tt=mid; 20 } 21 if(tt==top+1)top++; 22 stk[tt]=a[i]; 23 } 24 printf("%d\n",top); 25 memset(stk,0,sizeof(stk)); 26 stk[1]=a[1],top=1; 27 for(int i=2;i<=cnt;i++) 28 { 29 int l=1,r=top,mid,tt=top+1; 30 while(l<=r) 31 { 32 mid=(l+r)/2; 33 if(stk[mid]<a[i])l=mid+1; 34 else r=mid-1,tt=mid; 35 } 36 if(tt==top+1)top++; 37 stk[tt]=a[i]; 38 } 39 printf("%d\n",top); 40 return 0; 41 }
By 浅夜_MISAKI