二分+贪心思路的一些例题(学习笔记)

这篇博客探讨了如何解决一系列与数列分段相关的算法问题,包括找到每段和的最大值最小值以及最短距离的最大化。博主介绍了二分查找和贪心策略在这些问题中的应用,并给出了详细的代码实现,展示了如何在限定条件下优化算法以达到最佳解。
摘要由CSDN通过智能技术生成

题目:数列分段

题目描述 对于给定的一个长度为N的正整数数列A[i],现要将其分成M(M≤N)段,并要求每段连续,且每段和的最大值最小。

关于最大值最小:例如一数列4 2 4 5 1要分成3段将其如下分段:[4 2][4
5][1]第一段和为6,第2段和为9,第3段和为1,和最大值为9。

将其如下分段:[4][2 4][5
1]第一段和为4,第2段和为6,第3段和为6,和最大值为6。并且无论如何分段,最大值不会小于6。所以可以得到要将数列4 2 4 5
1要分成3段,每段和的最大值最小为6。

输入 输入文件divide_b.in的第1行包含两个正整数N,M,第2行包含N个空格隔开的非负整数A[i],含义如题目所述。

输出 输出文件divide_b.out仅包含一个正整数,即每段和最大值最小为多少。 样例输入 5 3 4 2 4 5 1

样例输出 6 题目说明 对于20%的数据,有N≤10;

对于40%的数据,有N≤1000;

对于100%的数据,有N≤100000,M≤N, A[i]之和不超过10^9。

题目思路
这是一道找最大值里的最小值的题目,很显然是用二分法求值。通过枚举答案来解。重点是如何写check函数,我们知道分段都是连续的,我们可以枚举答案的时候,利用尝试的答案来将数列分段,那么分段的结果就有分成了大于M段,或者小于等于M段。
check函数的思想就是拿这个mid值来枚举分段,在保证每一段不会大于mid值,来看看会分成几段,因为mid值这里看作段的最大值,如果分的段数小于输入给的段数,说明这个mid还不是段最大值中的最小值,继续缩小范围,再找最小

举例代码实现过程:

5 3
4 2 4 5 1

left=5,right=16 mid=10(每一段的最大值肯定大于单个数,并且是最大值,所以肯定大于最大的数,但每一段不可能大于所有的总和,所以right等于总和)
mid=10传入check函数,4+2+4为第一段,因为加上下一个,sum就大于10了,然后cnt++,sum变成现在的5,5再加下一个1,还是没有大于10,所以cnt小于m(3) 所以return false
说明mid这个数找的太大了,然后变成(5,10),mid=7
mid=7传入check函数,4+2后sum为6没有大于7,但是加上下一个4,sum大于7,所以cnt++,sum变成了现在的4,加上下一个5后,又大于了7,所以cnt++,sum变成现在的5,加上下一个1。所以cnt=2,还是false
下一个数是mid=6 同样的道理cnt=3,还是false,right=6;
然后下一个mid=5,返回true,left=6
跳出了循环,输出right=6;

code:


#include <iostream>
#include <cstring>
#include <cmath>
#include <string>
#include <cstdio>
#include <vector>
#include <algorithm>
#define LL long long
#define INF 99999999
using namespace std;
int n, m;
int l, r, mid;
int a[100005];
 
bool check(int x){
	int sum = 0;
	int cnt = 1;
	for(int i = 0; i < n; i++){
		sum += a[i];
		if(sum > x){
			cnt++;
			sum = a[i];
		}
	}
	if(cnt > m) 
		return true;
	else
		return false;
}
 
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 0; i < n; i++){
		scanf("%d",&a[i]);
		r += a[i];
		l = max(l,a[i]);
	}
	while(l < r){
		mid = (l+r) >> 1;
		if(check(mid))
			l = mid + 1;
		else
			r = mid;
	}
	
	printf("%d\n",r);
	return 0;
	}

变型题:

对于给定的一个长度为N的正整数数列Ai,现要将其分成连续的若干段,并且每段和不超过M(可以等于M),问最少能将其分成多少段使得满足要求。

Input 第1行包含两个正整数N,M,表示了数列Ai的长度与每段和的最大值,第2行包含N个空格隔开的非负整数Ai,如题目所述。

Output 一个正整数,输出最少划分的段数。

Samples
Input
5 6 4 2 4 5 1
Output
3

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e9;
int n;
int m;
int a[100005]; 
int sum;
int ans;
ll read() {
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9') {
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}
void out(ll x) {
	int stackk[40];
	if (x < 0) {
		putchar('-');
		x = -x;
	}
	if (!x) {
		putchar('0');
		return;
	}
	int top = 0;
	while (x) stackk[++top] = x % 10, x /= 10;
	while (top) putchar(stackk[top--] + '0');
}

int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
	}
	for(int i=1;i<=n;i++)
	{
		if(sum+a[i]<=m){
			sum+=a[i];
		}else{
			ans++;
			sum=a[i];
		}
		
		if(i==n&&sum<=m){
			ans++;
		}
		
	}
	out(ans);
	return 0;
	
	
}

题目:疯牛问题

农夫 John 建造了一座很长的畜栏,它包括N (2 <= N <= 100,000)个隔间,这些小隔间依次编号为x1,…,xN (0
<= xi <= 1,000,000,000). 但是,John的C (2 <= C <=
N)头牛们并不喜欢这种布局,而且几头牛放在一个隔间里,他们就要发生争斗。为了不让牛互相伤害。John决定自己给牛分配隔间,使任意两头牛之间的最小距离尽可能的大,那么,这个最大的最小距离是什么呢?
输入 有多组测试数据,以EOF结束。 第一行:空格分隔的两个整数N和C 第二行——第N+1行:分别指出了xi的位置 输出
每组测试数据输出一个整数,满足题意的最大的最小值,注意换行。
样例输入
5 3
1 2 8 4 9
样例输出
3

题意:简单的说就是给你一段长度,在这一段中给出m个点,然后在这m个点中选出c个点,让这c个点之间相邻两个点的之间距离的最小值最大

思路:通过二分枚举这个最小值,然后通过贪心的思想找出满足要求的最大的这个最小值

judge函数就是以段找,如果找到了一段大于等于我传入的最小值,cnt++,但是保证这一段距离是小于mid最小值的,如果最后分出来的段数是大于输入给的c个点,说明可以继续增加试探,mid变大,因为这还不是最小值中的最大值。

code:


#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
int n, c;
int pos[100005];
 
bool judge(int k)    
{
    int cnt = 1;
    int st = pos[0];
    for(int i = 1; i < n; ++i)
    {
        if(pos[i] - st >= k)
        {
            ++cnt;
            if(cnt >= c)
                return true; 
            st = pos[i];
        }
    }
    return false;
}
 
int binary_search()  // 二分枚举满足条件的最大距离 
{
    int left = 0;
    int right = pos[n-1] - pos[0];
    int mid = (left + right) >> 1;
    while(left <= right)
    {
        if(judge(mid))  // 所求距离 >= mid,可以继续增大试探 
            left = mid+1;
        else            // 所求距离 < mid,所以必须减小来试探 
            right = mid-1;
        mid = (left + right) >> 1;
    }
    return left-1;
}
 
int main()
{
    while(~scanf("%d%d", &n, &c))
    {
        for(int i = 0; i < n; ++i)
         scanf("%d", &pos[i]);
        sort(pos, pos+n);
        printf("%d\n", binary_search());
    }
    return 0;
}

题目描述:(火车站)

在这里插入图片描述
输入

2 2
4 106

输出

34

code:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=100005;
long long a[MAXN];
int ll,n,k;
long long judge(long long mid)
{
int m=0;
for(int i=2;i<=n;i++)
if(a[i]-a[i-1]>=mid)
{
m+=(a[i]-a[i-1])/mid;
if((a[i]-a[i-1])%mid==0)
m--;
}
if(m>k)
return 0;
return 1;
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
cin>>a[i];
sort(a+1,a+1+n);
long long l=0;
long long r=1000000000000;
while(l<r)
{
long long mid=(l+r)/2;
if(judge(mid)==1)
r=mid;
else
l=mid+1;
}
cout<<l<<endl;
return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值