二分
一、二分查找:要求在log的时间内在一串数中找出其中一个数
- 二分答案必须保证单调性,所以先要将序列排一个序
- 每次将区间二二分,将值与中间的数进行比较,根据比较情况分区间查找
- 这样的话必定能舍弃一个区间
- 所以总的时间为 O ( l o g n ) O(logn) O(logn)
例:在一串序列中找出 ≤ x \leq x ≤x的最大的那个数
int l=1,r=n;
while (l+1<r){
int mid=l+r>>1;
if (a[mid] == x) return mid;
if (a[mid] > x) r = mid;else l = mid;
}
return a[r] <=x ? r:l
一、present
- 每次二分答案最大化序列的最小值
-
- 对原有的序列维护差分
- 从左往右枚举,如果当前数<mid,那么就将 [ i − i + w − 1 ] [i-i+w-1] [i−i+w−1]的数所有都加上(mid-a[1])+1
- 这个东西可以用队列维护
- 这样遍历一遍复杂度是 O ( n ) O(n) O(n)
- 总的复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
- 似乎大致懂了
二、packmen
- 二分时间
- 考虑贪心
- 从左往右扫,分情况讨论:
- 1、如果扫到的是食物,那么就扫它右边的人,看距离是否小于mid
- 2、如果扫到的是人,那么就考虑他右边的食物以及他左边的食物
- 设他到食物的距离为d
- 如果3d<mid,那么就先左后右,否则先右后左(画图解放陈即可)
- 扫完一个人清空他以及他的路径
三、tallshoes
- 二分鞋子的高度
- 对于鞋子的高度进行重新建图
- 如果当前边的高度小于鞋子的高度,那么就把边的边权定为高度上调 ( 鞋 子 − 高 度 ) 2 (鞋子-高度)^2 (鞋子−高度)2,否则为0
- 跑单元最短路,看最小值是否满足金币数即可
四、金字塔
- 二分答案中位数之后将问题转化为01序列问题
- 然后好像是根据是否连续、相邻讨论
- 最中间的两个相邻的数即为答案
- 如果没有相邻的数那么就根据层数奇偶性判断
- 没怎么听懂
二分+dp
一、 levko and array
- 考虑二分答案,二分差值(答案)
- 修改不超过K个,即不修改的数 ≥ n − k ≥ n-k ≥n−k
- d p I dp_I dpI表示 a i a_i ai不修改的情况下i-1最多有多少个数不用修改
- d p j + 1 − > d p i ( m i d ∗ ( j − i ) ≤ ∣ a [ i ] − a [ j ] ∣ ) dp_{j+1}->dp_i(mid*(j-i)\leq |a[i]-a[j]|) dpj+1−>dpi(mid∗(j−i)≤∣a[i]−a[j]∣)
二分求最长上升子序列
- f I f_I fI表示长度为i的上升子序列末尾元素最小是多少
- 能够证明,f数组具有单调性
- 设m是当前上升子序列的长度
- 若 a i > f m a_i\ >f_m ai >fm,则令 f [ + + m ] = a [ i ] f[++m]\ =\ a[i] f[++m] = a[i]
- 如果想让 a i a_i ai满足作用,只有满足 f j < a i < f j + 1 f_j\ <\ a_i\ <\ f_{j+1} fj < ai < fj+1
- a I a_I aI之多只有一个
- 便可以通过二分答案来查找
Code
f[0] = -inf;
m = 0;
for (int i=1;i<=n;i++){
if (a[i]>f[m]) f[++m] = a[i];//能够直接更新
int res = 0 , l = 0,r = m;
while (l+1<r){
int mid = l+r>>1;
if (f[mid] < a[i]) res = mid,l = mid;else r = mid;
}//二分查找f_j<a[j]的 j,说明可以继续组合
if (f[l] < a[i]) res = l;else res = r;
f[res+1] = min(f[res+1] , a[i]);//尽可能小
}
线段树上二分
应用:求带修改全局第k大,区间第k大
int ask(int p,int k){
//线段树编号为p,查询第k大个数
if (l == r) return tr[p].ans;
int mid = l+r>>1;
if (tr[p<<1].sz >= k)
return ask(p<<1 , k);
else return (p<<1|1,k-tr[p<<1].sz);//由于权值线段树权值单调递增,左边已经有了size个数,这是第k大就是右边找一个第k-size个数
}
int ask(int p,int x,int y,int k){
//线段树编号为p,查询第k大个数
if (l == r) return tr[p].ans;
int mid = l+r>>1;
int tmp = tr[x].sz - tr[y].sz;
if (tmp >= k)
return ask(p<<1 ,tr[x].l,tr[y].l, k);
else return (p<<1|1,tr[x].r,tr[y].r,k-tmp);//由于权值线段树权值单调递增,左边已经有了size个数,这是第k大就是右边找一个第k-size个数
}
整体二分
- 巧妙的利用了离线的特点,把所有的修改、询问放在一起考虑
- 在递归处理过程中本质是在做二分答案,批量的处理一串的操作和询问
- 递归到底层是就返回值
一、待修改的区间第k大
- 我屮艸芔茻 。。似乎没跟上
二、You are given a tree
- 详情请看我昨天的笔记(虽然是假的)