【二分答案】

【二分答案】

二分+贪心 重点在贪心(check函数)

思路

只要满足单调性就都可以二分答案!!!
单调性:假设x为答案 x左边一个性质 x右边一个性质
可优化O(n) -> O(logn)

【题目整理】

跳石头

https://ac.nowcoder.com/acm/contest/22353/C
重点 设计check函数

代码

#include<bits/stdc++.h>
using namespace std;
const int N=50010;
long long l,d[N],jian[N];
int m,n;
//如何检查:为达成x必须要移走的石头(模拟!!!) 
bool check(long long x){
	int tot=0;//当前需要挪走的石头数量
	int i=0;//下一个要跳到的石头 
	int now=0;//现在在的石头
	while(i<=n){//跳到终点才算结束 
		i++;
		if((d[i]-d[now])<x){//要把这块石头挪走 
			tot++;
		}
		else{//跳过去 
			now=i;
		} 
	}
	//判断该x下需要挪走的石头数是否大于原先计划 
	if(tot>m) return false; 
	else return true;
}
int main(){
	cin>>l>>n>>m;
	for(int i=1;i<=n;i++){//从1开始读:有第0块石头和第n+1块石头的存在 
		cin>>d[i];
	}
	d[n+1]=l;//最后一个石头要存! 
	//二分查找最短跳跃距离 找最大值(向右查找) 
	long long ll=1,rr=l;
	while(ll<rr){
		long long mid=(ll+rr+1)/2;
		if(check(mid)) ll=mid;
		else rr=mid-1;
	}
	cout<<ll;
	return 0;
}

Greedy Gift Takers

https://ac.nowcoder.com/acm/contest/22353/D
重点
看着和二分没关系的题->答案可以一个个找->二分降复杂度
->验证找到的答案的准确性->设计check函数

//拿不到礼物的一定是后面的 
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n;
int c[N],b[N];
int ans=0;
bool check(int x){
	int tail=n-x;//如果前x头牛能够拿到礼物:拿完后一定排在后面->记录插到队尾往前数的第几个 
	for(int i=1;i<x;i++) b[i]=c[i];//前i-1(第i头正好拿到)头牛的信息->排序找 
	sort(b+1,b+x);
	for(int i=1;i<x;i++,tail++){//注意插几个要同步移动->前面的点移到后面 界限更多 
		if(b[i]>tail) return false;//超过界限->牛需要插到更前面的位置->牛无法插到合适的位置 
	}
	return true;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&c[i]);
	}
	int l=1,r=n;
	//找收得到礼物的最大值 
	while(l<r){
		int mid=(l+r+1)>>1;
		if(check(mid)) l=mid;
		else r=mid-1;
	}
	ans=n-l;
	printf("%d",ans);
	return 0; 
}

整点巧克力

https://ac.nowcoder.com/acm/contest/22353/1003
重点
在找到答案之后还需要check一遍找正确的吃巧克力序列

//跳石子思想 
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=500010;
int n,d;
ll a[N],l=0,r;
int s[N];//每次跑都更新一遍
bool check(ll x){//在保持比x大的状态下能坚持多少天 
	ll sum=0;
	int cnt=0;
	for(int i=1;i<=d;i++){//枚举天数 
		while(sum<x){
			sum+=a[++cnt];
			if(cnt>n) return false;//巧克力数不够
			s[cnt]=i;
		}
		sum>>=1;
	}
	for(int i=cnt+1;i<=n;i++) s[i]=d;
	return true;
}
signed main(){
      ios::sync_with_stdio(0);
      cin.tie(0);
      cout.tie(0);
      cin>>n>>d;
      for(int i=1;i<=n;i++){
      	cin>>a[i];
      	r+=a[i];
	}
	//二分答案
	while(l<r){
		ll mid=(l+r+1)>>1;
		if(check(mid)) l=mid;
		else r=mid-1;
	}
	cout<<l<<'\n';
	check(l);//最后还需要调用一遍答案->来更新s找到第几天吃的 
	for(int i=1;i<=n;i++) cout<<s[i]<<'\n';
      return 0;
}

智乃的小球

https://ac.nowcoder.com/acm/contest/95335/E

思路

小球相碰直接当成穿过
小球相遇所需时间就是距离/2
->找第k小的距离->距离满足单调性
->二分距离 验证该距离下k与所求k的关系

代码

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef pair<int,int> PII;
typedef long long ll;
typedef long double ld;
ll abss(ll a){return a>0?a:-a;}
ll max_(ll a,ll b){return a>b?a:b;}
ll min_(ll a,ll b){return a<b?a:b;}
bool cmpll(ll a,ll b){return a>b;}
const ll INF=1000000002;
int n;
ll k;
vector<ll> u,v;
/*
【思路】
小球相撞->直接当成穿过去就行
有单调性:距离增加的趋势->二分
->check函数写在该距离下能碰撞几次:<k 答案给小了 >k 答案给大了
*/
ll check(ll t){
      ll res=0;
      int p1=0,p2=0;//找右边第一个和距离内最后一个
      for(auto &i:u){
            //v下标从0开始(x
            while(p1<v.size() && v[p1]<i) p1++;
            while(p2<v.size() && v[p2]<=i+t) p2++;
            res+=p2-p1;
      }
      return res;
}
signed main(){
      ios::sync_with_stdio(0);
      cin.tie(0);
      cout.tie(0);
      cin>>n>>k;
      for(int i=1;i<=n;i++){
            ll p,q;
            cin>>p>>q;
            if(q==1) u.push_back(p);
            else v.push_back(p);
      }
      sort(u.begin(),u.end());
      sort(v.begin(),v.end());
      ll lu=u.size(),lr=v.size();
      //二分板子没问题
      ll l=0,r=1e9+2;
      while(l<r){
            ll mid=(l+r)/2;
            if(check(mid)>=k) r=mid;
            else l=mid+1;
      }
      ld ans=(ld)r/2;
      if(r==INF) cout<<"No"<<endl;//答案趋于无限大:找不到数->无解(不用想无解结论(x
      else{
            cout<<"Yes"<<endl;
            cout<<fixed<<setprecision(6)<<ans<<endl;
      }
      return 0;
}

中位数

https://qoj.ac/contest/2182/problem/12367

题目大意

每次可以选3个相邻的数合并成他们的中位数
问最后留下来的那个数

思路:【转化问题】

(1)最后结果要最大->二分答案
(2)中位数一般处理方法 -> >=mid就设成1 <mid就设成0

->check贪心思路:去掉尽可能多的0 让1的数量>0的数量->合并完才有可能是1
->为了保证[1的数量>0的数量]:两种合并方式出0->压缩0的个数
    要么三个0 要么2个0 1个1
    ① 000->0
    ② 010->0 这种是没办法必须合并
    (注意不会有100这种情况需要合并的
      要么是0100  有010已经合并过了
      要么是11000 等3个0一起合并
    (尽量不要001就合并 如果有0011这样就不需要消耗1的个数

代码

int n;
vector<int> a;
//000
void upgrade1(vector<int> &res){
    if(res.size()>=3){
        int l=res.size();
        int cnt1=res[l-1],cnt2=res[l-2],cnt3=res[l-3];
        if(cnt1==0 && cnt2==0 && cnt3==0){
            res.pop_back();
            res.pop_back();
        }
    }
}
//010
void upgrade2(vector<int> &res){
    if(res.size()>=3){
        int l=res.size();
        int cnt1=res[l-1],cnt2=res[l-2],cnt3=res[l-3];
        if(cnt1==0 && cnt2==1 && cnt3==0){
            res.pop_back();
            res.pop_back();
        }
    }
}
bool check(int x){
    //cout<<x<<endl;
    vector<int> tmp(n+1,0);
    for(int i=1;i<=n;i++){
        if(a[i]>=x) tmp[i]=1;
        else tmp[i]=0;
    }
    vector<int> v;
    for(int i=1;i<=n;i++){
        v.push_back(tmp[i]);
        upgrade1(v);
        upgrade2(v);
    }
    int c0=0,c1=0;
    for(auto son:v){
        //cout<<son<<" ";
        if(son==0) c0++;
        else c1++;
    }
    //cout<<endl;
    return c1>c0;
}
void solve(){
    cin>>n;
    a.resize(n+1,0);
    for(int i=1;i<=n;i++) cin>>a[i];
    int l=0,r=1e9;
    while(l<r){
        int mid=(l+r+1)/2;
        if(check(mid)) l=mid;
        else r=mid-1;
    }
    int ans=l;
    cout<<ans<<endl;
}
posted @ 2025-01-29 18:28  White_ink  阅读(20)  评论(0)    收藏  举报