洛谷二分答案问题
二分答案关键有2点
1. 怎么写judge函数,其实就是根据题意想办法判断我们枚举的这个答案是否可行(合法)。
2.找到了一个可行解(合法的,超过题目限制是不合法),再往左边还是右边查找看是否有更优的解是个问题,需要好好想想。
(这两点想明白了,二分就会异常简单甚至比暴力枚举还要简单。因为暴力枚举你还要考虑=0不移动时的特殊情况,而二分枚举就不用考虑特殊边界等特殊情况,!所以推荐直接二分查找枚举答案)
P2678跳石头:https://www.luogu.org/problemnew/show/P2678#sub
题意方面:最开始就是不知道它最小最大是什么意思。。。
题目讲的很绕,很烦,理清了后本质就是:求的是去掉几个数,所有得可能的排列情况中最小距离,再取这些最小距离中最大的那一个就是答案(最小值最大化)
一阶段:想的是搜索,找出所有剩N-M个数的排列(升序不重复),求出最小值,再在这些最小值中取最大的。(这是最好想的也最暴力的方法,但严重超时!)
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 using namespace std; 5 const int inf=0x7fffffff; 6 int n,B,a[100001]; 7 int used[100001]; 8 int Min=1e9,Max=0; 9 void so(int last,int step) 10 { 11 if(step==B+1) 12 { 13 Min=1e9; 14 for(int i=1;i<=B-1;i++) 15 { 16 Min=min(Min,used[i+1]-used[i]); 17 } 18 Max=max(Max,Min); 19 return; 20 } 21 22 int K=B-step; 23 for(int i=last;i<=n;i++) 24 { 25 //if(i+K>n) break; 26 used[step]=a[i]; 27 so(i+1,step+1); 28 } 29 } 30 31 int main(){ 32 scanf("%d%d",&n,&B); 33 for(int i=1;i<=n;++i)scanf("%d",&a[i]); 34 sort(a+1,a+n+1); 35 36 so(1,1); 37 38 printf("%d\n",Max); 39 40 return 0; 41 }
二阶段:看了某位大佬的一句话,“正向求出答案不好入手,求解答案远远没有验证答案简单“!(重大启发啊)
而且题目本来就是要求所有情况中最大的,本来就是暴力,就看你怎么优化暴力了,只是现在仅仅想到验证答案。就从头到尾一个一个验证答案是否符合,<=M次都是符合的选出一个最大的就行,只要>M后面就肯定更大,所以可直接结束!可以发现这就是线性查找(效率很低,所以有了后面的二分查找优化!但理解暴力算法也很重要),2000ms+过了。。
注意:暴力循环时<M(错误)和<=M(正确)的问题区别!虽然题目是单调递增性质,=N也一定比<M距离大,但有可能有特殊数据啊,有这样一句话“可能最后答案偏偏没有用完M呢”!恍然大悟!
1.像0,6这样移动0次肯定要比移动1次更大啊(你都没法取到1次,所以最终还是0);同理0,5,6移动1次肯定比移动2次甚至更多要优啊!(你都没法取到2次,最大到L取1次而已,没用完M)
2.就像上面情况,出题人专门给你个偏大的M,答案就是不用完M时才能取到,这时你只判断==M时不就错了吗!
3.也就是说,题目一定存在这样的点,<M一定比=M解更优,也一定存在这样的情况!(不信可参考进击的奶牛5,6点!只要<M过=M就不用要。)
4.关键特殊1
最终终于找到最后的bug了!很简单嘛,他如果给你一个故意偏大的M你肯定取不完,所以答案肯定就是<M是更优啊!而你的程序只判断==M就是0输出L也大多正确。如下特殊数据:!
1 2 3 4 5 6
取走4个点是最优的!如果给你M=5或6或更多!你就取不到!(因为循环答案最多从1到a[n]-a[1]即5!所以最多取到4)
5.关键特殊2
1 2 3 4
1,0次(发现并没有M=1次时的情况!关键,你只找M==1的答案,结果没有,所以就0输出L错,实际答案应该是1,移动1次最近距离还是1!)
2,2次
3,2次
总之,关键,你只直接判断M次是有问题的,因为这个M次答案可能没有,要么比M多,要么比M少,跳跃性的,像1次,这时候就只能往前走了!退一步答案小一点即<M也勉强算最优解!(因为你M多了不够,只能舍弃往前走了!)
(所以移动次数多距离大递增性没错,但没有你移动这个次数的答案你不得只能舍弃往前走了!!(<M成立在这!舍弃没用部分往前取小的最优),而不能往后走因为你次数不够!)
所以啊,你想不到的东西不代表它不存在,只是你没想到,想出来时是如此激动人心!(就是太花时间了,想了整整两天~~)
1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 using namespace std; 5 const int maxn=1e6+5; 6 int a[maxn]; 7 int L,N,M; 8 9 int judge(int x) 10 { 11 int last=0,cnt=0; 12 for(int i=1;i<=N+1;i++) 13 { 14 if(a[i]-a[last]<x) cnt++; 15 else last=i; 16 } 17 18 return cnt; 19 } 20 21 int main() 22 { 23 ios::sync_with_stdio(false); cin.tie(0); 24 25 cin>>L>>N>>M; 26 for(int i=1;i<=N;i++) cin>>a[i]; 27 28 sort(a+1,a+1+N); 29 a[N+1]=L; 30 int ans=0; 31 for(int i=1;i<=L;i++) 32 { 33 int t=judge(i); 34 if(t<=M)//必须是<=M次,不要认为只有=M次时才最远(虽然是线性增长的,但有特殊数据,像只有起终2数0次移动便是最好的)(也就是<M次也可以) 35 { 36 ans=max(ans,i); 37 } 38 else break;//后面更大 39 } 40 41 cout<<ans<<endl; 42 43 return 0; 44 45 }
三阶段:便是对暴力线性验证的优化,二分查找答案验证。70ms过!
1 #include <iostream> 2 #include <string> 3 #include <algorithm> 4 #include <cstdio> 5 #include <cstring> 6 using namespace std; 7 typedef long long ll; 8 const int maxn=1e6+5; 9 int a[maxn]; 10 int L,N,M; 11 12 int judge(int x) 13 { 14 int last=0,cnt=0; 15 for(int i=1;i<=N+1;i++) 16 { 17 if(a[i]-a[last]<x) cnt++; 18 else last=i; 19 } 20 21 return cnt; 22 } 23 24 int main() 25 { 26 ios::sync_with_stdio(false); cin.tie(0); 27 28 cin>>L>>N>>M; 29 for(int i=1;i<=N;i++) cin>>a[i]; 30 31 sort(a+1,a+1+N); 32 a[N+1]=L; 33 int l=1,r=L,ans=0; 34 while(l<=r) 35 { 36 int mid=(l+r)/2; 37 int t=judge(mid); 38 39 if(t>M) r=mid-1;//太大了,后面更大 40 else { ans=mid; l=mid+1; }//t<=M次就是可行解,扩大范围试试还有没有更大解 41 } 42 43 cout<<ans<<endl; 44 45 return 0; 46 }
完。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步