「基础算法」第3章 二分算法课堂过关

「基础算法」第3章 二分算法课堂过关

A. 【例题1】数列分段

题目

思路

挺简单的一道题,看到"最大值最小"就知道基本是二分答案+贪心(其实看到专题也应该知道)

二分枚举最小的最大值,然后:

bool check(int maxn) {
	int cnt = 1;
	int sum = 0;
	for(int i = 1 ; i <= n ; i++) {
		if(a[i] > maxn)	return false;//l如果取的是a中的最大值,应该不用此特判
		if(sum + a[i] > maxn) //如果当前值和前面的划分为同一段会超过限制
			++cnt , sum = 0;//则把重新开一段
		sum += a[i]; 
	}
	return cnt <= m;//是否能在m段以内(包含m)划分,使每段之和的最大值不超过maxn
}

代码

#include <iostream>
#include <cstdio>
#define nn 100010
using namespace std;
int read() {
	int re = 0 ; bool sign = 0 ; char c;
	do if((c = getchar()) == '-') sign = 1; while(c < '0' || c > '9');
	while(c >= '0' && c <= '9') re = (re << 1) + (re << 3) + c - '0' , c = getchar();
	return sign == 0 ? re : -re;
} 
int n , m;
int a[nn];

bool check(int maxn) {
	int cnt = 1;
	int sum = 0;
	for(int i = 1 ; i <= n ; i++) {
		if(a[i] > maxn)	return false;//l如果取的是最大值,应该不用此特判
		if(sum + a[i] > maxn) 
			++cnt , sum = 0;
		sum += a[i]; 
	}
	return cnt <= m;
}
int main() {
	n = read();
	m = read();
	int l = ( 1 << 29) , r = 0;
	for(int i = 1 ; i <= n ; i++) {
		a[i] = read();
		if(l > a[i])//这里l应该是取最大值,当时写错了,就懒得改了
			l = a[i];
		r += a[i];
	}
	
	
	while(l < r) {
		int mid = (l + r) / 2;
		if(check(mid))
			r = mid;
		else
			l = mid + 1;
	}
	cout << l ;
	return 0;
} 

B. 【例题2】防具布置

题目

思路

题目里面很重要的一句话就是:

但是整个防线上有且仅有一个位置有破绽或根本没有破绽

这决定了我们要用二分解题

我们二分举这个"破绽"的位置(设这个为\(pos\)),计算出0~pos之间"防具"的数量(设为\(sum\)),如果\(sum\)为奇数,则说明"破绽"在pos的左边(或者就是pos),否则在pos右边.

至于如何计算"防具"的数量,就自己慢慢思考吧

更多问题,代码见

代码

#include <iostream>
#include <cstdio>
#define nn 200010
#define ll long long
#define uni long long
using namespace std;
ll read() {
    ll re = 0;
    bool sign = 0;
    char c;
    do
        if ((c = getchar()) == '-')
            sign = 1;
    while (c < '0' || c > '9');
    while (c >= '0' && c <= '9') re = (re << 1) + (re << 3) + c - '0', c = getchar();
    return sign == 0 ? re : -re;
}
int n, m;
ll s[nn], e[nn], d[nn];

#define min_(_, __) (_ < __ ? _ : __)
bool check(ll pos) {//统计0~pos位置的防具数量
    ll sum = 0;
    for (int i = 1; i <= n; i++) {
        if (s[i] <= pos) {
            sum += (min_(pos, e[i]) - s[i]) / d[i] + 1u;
        }
    }
    return (sum & 1) == 0;
}
signed main() {
    ll T;
    T = read();
    while (T--) {
        n = read();
        ll l = 0, r = (1ll << 32);
        for (int i = 1; i <= n; i++) {
            s[i] = read(), e[i] = read(), d[i] = read();
        }

        if (check(r)) {//没有破绽的情况(全段的"防具"数量和为偶数)
            puts("There's no weakness.");
            continue;
        }

        while (l < r) {
            ll mid = (l + r) / 2;
            if (check(mid))
                l = mid + 1;
            else
                r = mid;
        }
        cout << l << ' ';

        ll sum = 0;
        for (int i = 1; i <= n; i++) {//计算l位置的防具数量
            if (s[i] <= l && e[i] >= l && (l - s[i]) % d[i] == 0)
                sum++;
        }
        cout << sum << endl;
    }

    return 0;
}

C. 【例题3】最大均值

题目

传送门(洛谷原题)

思路

设平均数为x,按照题目的要求

\[\frac{ \sum_{i=l}^{r} a_i}{r-l+1} \geq x (r-l+1\geq m) \]

要让x最大化

不难想到,原式可以变成这样:

\[\sum _{i=l}^{r}(a_i-x) \geq 0 (r-l+1\geq m) \]

所以,我们考虑二分枚举x,把a中的数全部减去x,存在b中,判断b中是否有一段长度大于等于m的区间,使得区间内数的和大于等于0

那问题来了,怎么判断呢(看了波题解)

首先,求出b的前缀和数组sum

然后,枚举每一个i,找到

\[sum_i - sum_j (0 \leq j \leq i-m) \]

的最大值,判断其是否大于等于0,因此,我们最小化\(sum_j\),就有了\(minv\):

minv维护的是:

\[\min\{sum_{j}\}(0\leq j\leq i-m) \]

随着i的增长,j的取值范围往右延伸一位,我们只需将minv和新增的一个比较即可

bool check(double aver) {
	for(int i = 1 ; i <= n ; i++)//求出b及其前缀和
		sum[i] = sum[i - 1] + (b[i] = a[i] - aver);
	double minv = 1e10 , ans = -1e10;//ans找最大值
	for(int i = m ; i <= n ; i++) {
		if(minv > sum[i - m])
			minv = sum[i - m];
		if(ans < sum[i] - minv)
			ans = sum[i] - minv;
	}
	return ans >= 0;
}

代码

#include <iostream>
#include <cstdio>
#define nn 100010
using namespace std;
int read() {
	int re = 0 ; bool sign = 0 ; char c;
	do if((c = getchar()) == '-') sign = 1; while(c < '0' || c > '9');
	while(c >= '0' && c <= '9') re = (re << 1) + (re << 3) + c - '0' , c = getchar();
	return sign == 0 ? re : -re;
} 
int n , m;
double a[nn] , b[nn];
double sum[nn];

bool check(double aver) {
	for(int i = 1 ; i <= n ; i++)
		sum[i] = sum[i - 1] + (b[i] = a[i] - aver);
	double minv = 1e10 , ans = -1e10;
	for(int i = m ; i <= n ; i++) {
		if(minv > sum[i - m])
			minv = sum[i - m];
		if(ans < sum[i] - minv)
			ans = sum[i] - minv;
	}
	return ans >= 0;
}
int main() {
	n = read();
	m = read();
	double l = 1e6 , r = -l;
	for(int i = 1 ; i <= n ; i++) {
		a[i] = (double)read();
		if(l > a[i])
			l = a[i];
		if(r < a[i])
			r = a[i];
	}
	
	while(r - l > 1e-5) {
		double mid = (l + r) / 2;
		if(check(mid))	l = mid;
		else r = mid;
	}
	cout << int(r * 1000);
	return 0;
} 
posted @ 2021-01-03 14:43  追梦人1024  阅读(91)  评论(0编辑  收藏  举报