[蓝桥杯 2022 国 B] 卡牌(贪心/二分)

文章讨论了一种优化算法的过程,从最初的模拟方法转换为使用二分查找和贪心策略。通过解决如何在限制补牌数量的情况下最大化卡牌套数的问题,展示了算法从低效到高效的改进。二分查找用于确定能组成的最大卡牌套数,而贪心策略确保在有限资源下尽可能接近最优解。
摘要由CSDN通过智能技术生成

题目传送门 

该题第一思路是想去模拟题目中所描述的过程

这里我选择从大到小遍历可能凑出的牌套数,计算凑出它需要补的牌数以及判断是否会超出能补的牌数

#include<iostream>
#include<climits>
#include<vector>
#include<algorithm>
#define int long long 

using namespace std;


const int MAX=2e5+10;

int a[MAX];
int b[MAX];
int n,m,ans=0,num=0;
vector<int>v;

signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		v.push_back(a[i]);
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
	}
	sort(v.begin(),v.end());//排序
	for(int i=n-1;i>=0;i--){//尝试将每一种卡牌都变成v[i]张 
		int ele=v[i];
		int sign=0;
		int sum=0; 
		for(int j=1;j<=n;j++){
			int need=v[i]-a[j];//需要补上 
			if(need>0)
			sum+=need;
			if(b[j]-need<0){
				sign=1; 
				break;//不能补 
			} 
		}
		if(sum>m||sign){
			continue;
		}else{
			cout<<v[i]<<endl;
			break;
		}
	} 
	return 0;
}

由于该算法效率低下并不能通过所有样例

但是仔细理解题意,我们可以发现该方法可以使用二分进行优化

#include<iostream>
#include<algorithm>
#define int long long
using namespace std;

const int MAX=2e5+10;

int n,m,l,r;
int a[MAX];
int b[MAX];

bool check(int x){
    int s = 0;
    for (int i = 0; i < n; i++) {
        if (x - a[i] <= b[i]) {
            s += max(x - a[i], 0ll);//统计组成x套牌需要补s张牌
        }
        else return false;//超出了b[i]
    }
    if (s <= m) return true;//没有越界
    return false;
}

signed main(){
	cin>>n>>m;
	for(int i=0;i<n;i++){
		cin>>a[i];
		l = min(l, a[i]);
	}
	for(int i=0;i<n;i++){
		cin>>b[i];
		r=max(r,a[i]+b[i]);
	}	
	int ans;
	while(l<=r){
		int mid=(l+r)/2;//二分组成的牌套数
		 if(check(mid)){
		 	ans=mid;
		 	l=mid+1;
		 }else{
		 	r=mid-1;
		 }
	}
	cout<<ans<<endl;
	return 0;
}

 

第二思路是使用“贪心”的思想进行解答

首先要理解一个东西,卡牌套数等于最少的卡牌牌数。因为一套卡牌需要所有卡牌各一张,所以对于最少的卡牌,它如果只有 x 张,则只能有 x 套卡牌含有最少的卡牌。再多的其他卡牌就没用了,所以卡牌套数等于最少的卡牌牌数。

那具体怎么贪心呢?使卡牌套数最多。由于卡牌套数等于最少的卡牌牌数,只需要尽量让最终各种卡牌数量接近即可。那就优先画数量少的卡牌,直到空卡牌用完。

每次 O(n) 选择最小的卡牌,复杂度会很高。由于这个贪心策略是要连续选择最小的,所以可以通过排序来降低复杂度。

最后还有一个问题:可画的卡牌数有限制。还是根据卡牌套数等于最少的卡牌牌数,在空卡牌够用的情况下,最终的卡牌套数取决于初始卡牌数与可画卡牌数的和最少的卡牌。所以可以判断目前求得的卡牌数是否大于每张卡牌初始卡牌数与可画卡牌数的和的最小值来解决这个问题。若小于,继续贪心。若大于,则证明空卡牌够用,但受可画的卡牌数的限制,卡牌套数只能为每张卡牌初始卡牌数与可画卡牌数的和的最小值。(好吧这一段有点难理解,可以结合代码里的注释理解)

#include<iostream>
#include<algorithm> 
#include<climits>
#define int long long

const int MAX=2e6;

using namespace std;

int n,m;
int a[MAX];
int b;

signed main(){
	int consume=0,minn=INT_MAX;
	cin>>n>>m;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	for(int i=0;i<n;i++){
		cin>>b;
		if(a[i]+b<minn){
			minn=a[i]+b;//得到最大套牌数 
		} 
	}
	sort(a,a+n);//其实无所谓对应下标 
	int ans=a[0];//初始值为最小牌数 
	for(int i=0;i<n;i++){
		if(i==n-1&&consume<m){
			ans+=(m-consume)/n;//剩余卡牌平均分 
			if(ans>minn)ans=minn;
			break; 
		}
		if(a[i]<a[i+1]){//前面卡牌不够 
			//这里不能且无需去修改数组中的内容(会降低代码效率)
			 consume+=(i+1)*(a[i+1]-a[i]);//消耗的卡牌
			 ans+=(a[i+1]-a[i]); 
		}
		if(consume>m)//不够牌补了
		{
			consume-=(i+1)*(a[i+1]-a[i]);
			ans-=(a[i+1]-a[i]);
			ans+=(m-consume)/(i+1);//平均分
			break; 
		 } 
		 if(ans>minn){
		 	ans=minn;
		 	break;
		 }
	}
	cout<<ans<<endl;
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZZZWWWFFF_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值