二分答案 杂题总结
话说这个专题和二分貌似没啥太大关系
二分答案的思想很简单,在不好直接求解的时候把求解转化为判定,由于判定的难度一般小于求解,所以经过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 }
先找到点集的上下左右四界,考虑每个边上的点都要被覆盖到,所以正方形都贴着边,由于只有三个正方形有四个边,所以必定有一个正方形贴着角
二分正方形边长,枚举第一个正方形在哪个角上,第二个也一样,看第三个能否覆盖剩下的点,相当于一个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的复杂度有时候能跑过百万,可以看看大帝的博客关于复杂度
这部分题比较注重细节,代码实现的时候要仔细

浙公网安备 33010602011771号