zoukankan      html  css  js  c++  java
  • 洛谷二分答案问题

    二分答案关键有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
    ",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 }

    完。

  • 相关阅读:
    计算机硬件介绍
    SC-控制Windows服务的命令
    SQL Server 2008中的Service SID 介绍
    内置系统账户:Local system/Network service/Local Service 区别
    用于sql server启动的账户
    sql server常见服务
    oracle和sql server的区别(1)
    架构设计学习(一)
    web项目分层设计
    封装继承多态
  • 原文地址:https://www.cnblogs.com/redblackk/p/9856758.html
Copyright © 2011-2022 走看看