例题_CF1168A 二分+贪心

在这里插入图片描述
传送门
题意分析:
给定一个n代表接下来一行有n个数,然后是m代表要取余的数,你可以进行一个操作:选择任意k个数让他们+1对m取余得到一个数,这个数可以代替原数组中的数,问:最少需要多少次操作使得n个数为不下降序列
解法:
二分答案+贪心,最初的思路是要用二分去写这个题,顾名思义是要二分最后输出的答案,然后写一个check函数去检查这个答案的可行性,怎么写check函数呢?最少的操作数!!!很重要 比如其中一个例子为
10 10
5 0 5 9 4 6 4 5 0 0
正常的思路是 我取那些不满足非递增序列的数及都小于两边的数或者都大于两边的数进行分析, 比如5 9 4 那么问题来了 我是让9变成5还是让4变成9好呢? 假如说让9变成5 需要6次操作,而9后面的数都要跟着变,,可知最少需要6次操作,而如果让4变成9需要5次操作而后面的最少需要9次操作,所以选择第一种操作比较合适,可是我们这样做贪心的条件是什么?思考了片刻发现有个可能超时的思路,就是把9变成5是依据9前面那个数也就是5来变得,而把4变成9是依据当前这个数9来变得,所以我可以用一个循环比较两种情况中的相对优的那次操作然后顺势贪心下去,可是有个问题是 有组样例绝对把这个思路卡死了,我只是把那些不满足递增的数进行了改变,并没有考虑那些符合条件的数比如一组样例
4 6
0 3 5 1
答案是3 果然我可以只进行三次操作 数组变成 0 0 2 4 而按我之前的思路却是4 所以我迷茫了,,,贪心果然贪错误了,那么怎么进行贪心+二分呢?

正解思路

我们二分的答案是什么? 最少操作数,我们现在需要解决什么?怎么验证这个最少操作数符合题意
那我们怎么贪心?贪心顾名思义是一种选择,我们可以只拿当前数和前一个数比较。注意这个前一个数是会变化的。那么什么情况不符合题意?如果当前数小于前一个数并且当前数+最少操作数也小于前一个数就一定不符合题意。那我们可以拿二分出来的答案做些什么?更新维护前一个数,如果当前数大于前一个数我们就需要选择是否维护,如果当前数+最少操作数-m小于前一个数就要更新前一个数,因为如果当前数大于前一个数我可以选择怎么降低当前数,这就是本题的贪心就是用二分出来的操作数去降低,如果说能降低 (num[i]+mid)%m>last那么就不用管了,那如果不能降低只能被迫更新了 所以这才是正解,只能怪自己贪心贪错了,呜呜 同时也知道了贪心+二分,一定要利用好二分出来的答案进行反向验证答案的可行性!!!

代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<cmath>
#define Xiaobo main
#define IO cin.tie(0),ios::sync_with_stdio(false);
using namespace std;
const int maxn=3e5+5;
const int mod=1e9+7;
const double eps=1e-15;
const double pi=acos(-1);
const int INF=0x3f3f3f;
typedef long long ll;
ll read(){ll c = getchar(),Nig = 1,x = 0;while(!isdigit(c) && c!='-')c = getchar();if(c == '-')Nig = -1,c = getchar();while(isdigit(c))x = ((x<<1) + (x<<3)) + (c^'0'),c = getchar();return Nig*x;}
ll qpow(ll a,ll b,ll m){ ll ans=1; while(b){ if(b&1) ans=ans%a%m;a=a*a%m,b>>=1;} return ans; }
ll qpow(ll a,ll b){ ll ans=1;while(b>0){ if(b&1) ans=ans*a;a*=a,b>>=1; } return ans;}
ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b);}
ll mul(ll a,ll b,ll m){ll res=0;while(b>0) { if(b&1) res=(res+a)%m;a=(a+a)%m;b>>=1;}return res;}
int n,m;
int num[maxn];
int a[maxn];
bool ok(int x) {
	int last=0;
	for(int i=1;i<=n;i++) {
		if(last>num[i]) {
			if(last-num[i]>x) return false;
		}
		else {
			if(m-num[i]+last>x) last=num[i];
		}
	}
	return true;
}
int Xiaobo()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) {
		cin>>num[i];
	} 
	int l=0,r=maxn,ans=0;
	while(l<=r) {
		int mid=(l+r)>>1;
		if(ok(mid)) {
			r=mid-1;
			ans=mid;
		}
		else {
			l=mid+1;
		}
	}
	cout<<ans<<endl;
}

加油 !!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值