分治算法
分治,字面上的解释是“分而治之”,就是把一个复杂的问题分成许多相似或者相同的子问题,再把子问题分成更小的问题,直到最后子问题可以直接简单求解,原问题的解即子问题的解的合并。
特征:
1.问题的规模缩小到一定规模就可以很容易的解决
2.问题可以分解成若干个规模较小的模式相同的子问题,即该问题具有最优子结构性质
3.合并问题分解出的子问题的解可以得到问题的解
4.问题所分解出的各个子问题之间是独立的,即子问题之间不存在公共的子问题
分治问题的实例:归并排序
代码如下:
void msort(int l,int r)
{
if (l == r)return;
int mid = (l + r) >> 1;
msort(l, mid);
msort(mid, r);
int k = l;
int i = l;
int j = mid + 1;
while (i <= mid && j <= r)
{
if (a[i] <= a[j])b[k++] = a[i++];
else b[k++] = a[j++];
}
while (i <= mid)b[k++] = a[i++];
while (j <= r)b[k++] = a[j++];
for (int t = l; t <= r; t++)
{
a[t] = b[t];
}
}
二分算法
概念:对于一个命题p(x=k),若存在k0使得k<k0时命题为真,当k>k0时命题为假,当k=k0时命题为真或假,则称命题p满足二分条件。二分算法能快速找到k0分界线。
一般步骤:
1.给出二分范围[l,r]
2.求范围中点mid=(l+r)/2
3.判断命题p(x=mid)的真假
4.若为真,则另l=mid,若为假,则另r=mid
5.循环2-4,直到精度满足要求,若精度为整数,则二分到r-l<=-1为止
模板如下
int binary_search(int l, int r)
{
int mid;
while (l + 1 > r)
{
mid = (l + r) >> 1;
(check(mid) ? l : r) = mid;
}
return check(r) ? r : l;
}
举例:洛谷P2249
#include<iostream>
using namespace std;
int a[1000001];
int bsearch(int l, int r, int k)
{
int mid;
while (l + 1 < r)
{
mid = (l + r) / 2;
if (a[mid] < k)l = mid;
else r = mid;
}
if (a[l] == k)return l;
if (a[r] == k)return r;
else return -1;
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
int k;
while (m--)
{
cin >> k;
cout << bsearch(1, n, k) <<" ";
}
return 0;
}
二分答案
二分答案是二分最常见的形式
所谓二分答案,是指当答案满足二分单调性时,用二分将最优化问题转化为可行性问题
所谓最优化问题转化为可行性问题,就是指问题往往要求在某条件下求出某值的最大值或者最小值,这正是二分答案的边界
因此当答案满足二分单调性时,就可以在外面套一层二分答案,把求某值的最大值转化成一个猜测的答案,要求判断此答案是否满足要求
这就是二分答案的真正意义
举例洛谷P2678
思路:
题目中有个关键字:最短跳跃距离尽可能长(最大的最小,最小的最大,这是二分答案的敏感词)
二分跳跃距离,并且把这个距离认为时最短跳跃距离,然后以这个距离为标准移石头,使用一个check去判断这个解的可行性如果这个解可行,则去它的右边二分,如果不可行,就去他的左边二分
check怎么实现呢??
开始时你在i=0的位置我在跳下一步的时候判断我下一次要跳跃的距离,如果这个距离比二分出来的mid小,则需要拿走前面的石头(因为比最小跳跃距离还小的距离是不合法的),移走之后计数器加一。
模拟完这个过程后,我们查看计数器的值,这个值代表着我们需要移走石头的数量,然后判断这个数量是不是超了,超了就返回false,否则返回true
#include<iostream>
#include<algorithm>
using namespace std;
int a[50010];
int l;
int m;
int n;
bool check(int mid)
{
int cnt=0;
int i = 1;//第一块石头
int j = 0;//起点
while (i < n + 1)
{
if ((a[i] - a[j]) < mid)cnt++;//如果当前石头与下一次跳跃的石头的距离小于mid,则移走这块石头
else j = i;
i++;
}
if (cnt <= m)return true;
else return false;
}
int bsearch(int l, int r)
{
int mid;
while (l + 1 < r)
{
mid = (l + r) / 2;
if (check(mid))
{
l = mid;
}
else r = mid;
}
return check(l) ? l : r;
}
int main()
{
cin >> l >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
a[n + 1] = l;
if (n != 0 && m != 0) cout << bsearch(1, l);
else cout << l;
return 0;
}