二分答案 杂题总结

话说这个专题和二分貌似没啥太大关系

二分答案的思想很简单,在不好直接求解的时候把求解转化为判定,由于判定的难度一般小于求解,所以经过logn次判定就能找到答案

关键是怎么判定答案合不合法,诸如最小的最大值,分割最小等等都转化为判定,判定方法一般有枚举,贪心,搜索,dp,图论算法等等,也相当于对前面的一个巩固提升吧

聪明的质检员——枚举判断

比较水的一道题,理解体面之后直接二分w,然后处理一个1到n整段前缀和,对于每个询问直接o1回答,累加答案之后再相乘判断是否合法

注意最后出来的答案要和ans比较下,ans初始化为极大值,还有注意开long long

栅栏——搜索 

对,就是爆搜,木板从小到大排序,二分能取到的木板数量,然后dfs搜索回溯划分方案是否合法,关键在剪枝

1.预处理前缀和,如果已有木板总长度不够直接判定不合法

2.相当于一个小贪心,对木材从大到小排序,木板从小到大排序,一旦某个木材无法满足,直接失败,这样可以降低搜索树浅层规模优化

3.关于浪费,如果一块木板切下来剩的东西啥也满足不了,相当于有效总长度减小了,如果s-waste<sum的话,直接失败

4.对于一块木板如果满足不了一个木材,那么对于比它大的木材也满足不了

5.这个不好想到,对于相等长度的木材,直接从上一个被满足的地方开始枚举(有专门针对这个的数据,加了就A了)

这个贴上代码

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 int a[55],b[1005],c[55],d[1005];
 4 int m,n,s,ss,ma,fail=99999999,p=1,last;
 5 int qz[1005],maa[1005];
 6 bool dfs(int x,int y)
 7 {
 8     if(qz[x]>ss||maa[x]>ma||fail<=b[x])return 0;
 9     if(y>x)return 1;
10     int i=0;
11     if(d[y]==d[y-1])i=last;//相等的直接从上一个位置枚举 (5) 
12     else i=1;
13     for(i;i<=m;i++) 
14     {
15       int k=0;
16       while(c[i]<d[y])
17       {
18           if(i>=m)
19           {
20               ss+=k;//注意加回来 
21             return 0; 
22           }
23           if(c[i]<b[1])k+=c[i];//浪费的如果比最小的小就没用 (4) 
24         i++;
25       }
26       last=i;
27       c[i]-=d[y];ss-=k;
28       if(dfs(x,y+1))return 1;
29       c[i]+=d[y];ss+=k;
30     }
31     return 0;    
32 }
33 int er(int l,int r)
34 {
35     if(l>=r)return r;
36     memset(c,0,sizeof(c));
37     memset(d,0,sizeof(d));
38     int ans=((l+r)/2)+1;
39     for(int i=1;i<=n;i++)c[i]=a[i];
40     for(int i=1;i<=ans;i++)d[i]=b[ans+1-i];//b从小到大降低浅层规模 
41     ss=s;
42     if(!dfs(ans,1))
43     {
44         fail=b[ans];//搜索失败剪枝,比他大也必定失败 (3) 
45         return er(l,ans-1);
46     }
47     else return er(ans,r);
48 }
49 signed main()
50 {
51     cin>>m;
52     for(int i=1;i<=m;i++)
53     {
54       scanf("%d",&a[i]);s+=a[i];
55       ma=max(ma,a[i]);    
56     }
57     cin>>n;
58     for(int i=1;i<=n;i++)scanf("%d",&b[i]);
59     sort(a+1,a+1+m);//排序降低搜索树规模 (2) 
60     sort(b+1,b+1+n);
61     for(int i=1;i<=n;i++)
62     {
63        qz[i]=qz[i-1]+b[i];//前缀和剪枝 (1) 
64        maa[i]=max(maa[i-1],b[i]);//最大值剪枝 
65     }
66     while(qz[n]>s)n--;
67     cout<<er(0,n);
68     return 0;
69 }
View Code

覆盖问—几何+搜索 

先找到点集的上下左右四界,考虑每个边上的点都要被覆盖到,所以正方形都贴着边,由于只有三个正方形有四个边,所以必定有一个正方形贴着角

二分正方形边长,枚举第一个正方形在哪个角上,第二个也一样,看第三个能否覆盖剩下的点,相当于一个dfs,由于深度小可以不用递归

 奶牛健美操——树形dp

砍一棵树成森林,让出来的树中直径最大的最小

回忆一下树形dp,对于一棵树,统计根节点所有儿子的深度,如果其中存在d1+d2+2>ans,就是直径大于二分出来的长度,就说明要砍一刀

每个儿子按深度从小到大排序,利用贪心的方法判断要不要砍,砍完了顺便把深度维护一下

一个dfs就行,递归到叶子再更新回去,可以证明每一个子树深度都不大于二分出来的值,只要看砍的次数是不是小于等于s就行了

木棍分割——dp

这个二分很简单,关键是统计方案,专门一篇博客


 

其他

一个二分的板子

1 int er(int l,int r)
2 {
3     if(l>=r)return r;
4     int ans=(l+r)/2;
5     if(chk(ans))return er(l,ans); 
6     else return er(ans+1,r);
7 }

这里注意边界判断,判断之后返回左右哪个区间跟具体题目有关系,可以和栅栏的二分比较一下,那个边界有点不一样

二分答案的时间复杂度有时候比想象的小,表面n(logn)^2的复杂度有时候能跑过百万,可以看看大帝的博客关于复杂度

这部分题比较注重细节,代码实现的时候要仔细

 

posted @ 2021-05-22 18:46  D'A'T  阅读(85)  评论(0)    收藏  举报