贪心(Greedy algorithm)

众所不周知,贪心就是一种每次都抢局部最优解的算法(这样的确很贪),但是想要证明贪心的正确性很难。但在比赛中,尤其是不会写DP的你,可以选择大胆尝试,写一个贪心,万一对了呢 (:

本期博客将几种贪心的方法。(前置知识:STL基本操作,不会的请先自行退出学习STL)

Part 1:multiset维护贪心

P1607:

既然目标是让更多的牛完成愿望,所以只要有空位,就让牛上去。但问题是有一些牛的旅程不太划算,会影响其他奶牛(老站着位),所以我们可以中途把它踢出去(非常人道主义)。思路就是这样,看代码吧。

#include <bits/stdc++.h>
using namespace std;
struct cow{
	int s;
	int e;
	int m;
}cattle[50005];
bool operator < (const cow& x,const cow& y){
	return x.s<y.s;
}
multiset<int> mst;
int main(){
	int k,n,c;
	cin>>k>>n>>c;
	for(int i=1;i<=k;i++)
		cin>>cattle[i].s>>cattle[i].e>>cattle[i].m;
	sort(cattle+1,cattle+k+1);
	int ans=0;
	for(int i=1;i<=k;i++){
		while(!mst.empty() && *mst.begin()<=cattle[i].s){
			mst.erase(mst.begin());
			ans++; 
		}
		while(cattle[i].m){
			if(mst.size()<c){
				mst.insert(cattle[i].e);
				cattle[i].m--;
			}
			else{
				auto itr=mst.end();
				itr--;
				if(cattle[i].e<*itr){
					mst.erase(itr);
					mst.insert(cattle[i].e);
					cattle[i].m--;
				}
				else
					break;
			}
		}
	}
	cout<<ans<<endl;//记得ans加上mst.size()
	return 0;
}

P2255:

这题要用一点回撤的思想。建议先阅读下面的Part 2在看此题。

说真的,数据小的离谱,让我不知所措。

其实每个节目是一个区间(和上题有些相似)。我们按右端点排序,录到新节目看有没有空的机子,如果没有,就做取舍,扫一遍就OK了。放个代码。

#include <bits/stdc++.h>
using namespace std;
struct program{
	int l;
	int r;
}programs[200];
bool operator < (const program& x,const program& y){
	return x.l<y.l;
}
multiset<int> mst;
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>programs[i].l>>programs[i].r;
	sort(programs+1,programs+n+1);
	for(int i=1;i<=n;i++){
		while(!mst.empty() && *mst.begin()<=programs[i].l)
			mst.erase(mst.begin());
		mst.insert(programs[i].r);
		ans++;
		if(mst.size()>2){
			mst.erase(prev(mst.end()));
			ans--;
		}
	}
	cout<<ans<<endl;
	return 0;
}

Part 2:优先队列回撤贪心

我们还是看题吧。

P4053:

此题一(亿)看就只是贪心,但是不会。这里有两个参数:修理建筑的时间和报废的时间。该怎么权衡这两个呢?大家可以先自己想一会。

有思路了吗?其实,没有你想的辣么难,我们可以按报废的时间排序,然后一个优先队列维护,每次加入一个建筑,只要发现它报废了,就把当前堆里最小的那个(也是top)踢出去就可以了。而这,就叫优先队列回撤贪心。怎么样,不难吧。然后放个代码。

#include <bits/stdc++.h>
using namespace std;
struct building{
	int t1;
	int t2;
}buildings[150005];
bool cmp(building a,building b){
	return a.t2<b.t2;
}
priority_queue<int> pq;
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>buildings[i].t1>>buildings[i].t2;
	sort(buildings+1,buildings+n+1,cmp);
	int sum=0,ans=0;
	for(int i=1;i<=n;i++){
		sum+=buildings[i].t1;
		pq.push(buildings[i].t1);//入队
		if(sum<=buildings[i].t2)
			ans++;
		else{//把堆顶踢出去
			sum-=pq.top();
			pq.pop();
		}
	}
	cout<<ans<<endl;
	return 0;
}

注意要开long long。

P3545:

思路和上一题大差不差。直接看代码吧,有注释。

#include <bits/stdc++.h>
using namespace std;
int a[250005],b[250005];
struct costumer{
	int req;
	int idx;
};
bool operator < (const costumer& x,const costumer& y){
	return x.req<y.req;
}
priority_queue<costumer> pq;//当前满足的顾客的堆,堆顶是需求最大的客户 
int ans[250005];
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	for(int i=1;i<=n;i++)
		cin>>b[i];
	long long ad=0,goods=0;//ad是总需求,goods是总进货数
	for(int i=1;i<=n;i++){
		goods+=a[i];//进货 
		pq.push((costumer){b[i],i});
		ad+=b[i];//尝试满足第i个人的需求
		if(ad>goods){//不够 
			ad-=pq.top().req;
			pq.pop();
		}
	}
	int tot=0;
	while(!pq.empty()){
		ans[++tot]=pq.top().idx;
		pq.pop();
	}
	cout<<tot<<endl;
	for(int i=1;i<=tot;i++)
		cout<<ans[i]<<' ';
	return 0;
}
十年OI一场空,不开long long见祖宗!

Part 3:相邻交换法

到这趴就得要亿点点数学了。老规矩,看题。

AcWing 58:

难得一道AcWing的题目。

我当时:乍一看,简单!再一看,诶,有点......难...... 再想一会,我根本不会......

言归正传,首先肯定是string输入输出滴,然后就是去排序。怎么个排法呢?

结论:如果 ab > ba,那么就交换a,b在数组内的位置!            But, WHY?

很多人应该都不理解(我一开始也不懂),但就是猜对了,很神奇。

首先,直接比较大小肯定不行。那怎么确定谁在左边呢?我们用位置原理展开一下。

假设A_{n-1}A_{n-2}......A_1A_0B_{n-1}B_{n-2}......B_{1}B_{0}是两个要比较的数。用位置原理得到这个鬼玩意:A_{n-1}*10^{n-1}+A_{n-2}*10^{n-2}+......+A_1*10+A_0,B同理。然后我们把A在左和B在左分别表示出来,用小于号连接,变成一个很神奇很长打的我都不想再打的一坨式子:10^m*(A_{n-1}*10^{n-1}+A_{n-2}*10^{n-2}+......+A_1*10+A_0)+B_{m-1}*10^{m-1}+B_{m-2}*10^{m-2}+......+B_1*10+B_0<10^n*(B_{n-1}*10^{n-1}+B_{n-2}*10^{n-2}+......+B_1*10+B_0)+A_{m-1}*10^{m-1}+A_{m-2}*10^{m-2}+......+A_1*10+A_0

然后开始抵消,A往左边放,B往右边放,就成了这样:(10^m-1)\times A<(10^n-1)\times B继续化简:A/(10^n-1)<B/(10^m-1)。好了,结束了,满足传递性,排序一遍就万事大吉了!!!!!!!!!!!

#include <bits/stdc++.h>
using namespace std;
string num[10005];
int main(){
	int n;
	cin>>n;
	for(int i=0;i<n;i++)
		cin>>num[i];
	sort(num,num+n,[](string x,string y){return x+y<y+x;});
	string ans="";
	for(string x:num)
		ans+=x;
	cout<<ans<<endl;
	return 0;
}

代码超简单。

P1080:

最后一题了。这题还是很有难度的(毕竟是提高组的)。首先,我们知道你交换两个大臣(x和y),其他大臣的奖金是不变的,只会影响它们两个本身。其次,这会出现四个值(xy顺序有两种,每个顺序还要分别考虑x和y的奖金),所以答案就变成了max(a,b)<max(c,d)(A是xy中的x,B是xy中的y,C是yx中的y,D是yx中的x)不太好处理了。没关系,把ABCD分别写出来,会发现AD同分母且A<D;BC同分母,且B>C。所以,B必然小于D因为max(a,b)<max(c,d),然后就找到了排序方法:

bool cmp(minister x,minister y){
	return x.l*x.r<y.l*y.r;
}

接下来就完事大吉了,注意需要高精(Python在狂笑)。

#include <bits/stdc++.h>
using namespace std;
struct minister{
	int l;
	int r;
}ministers[1005];
int product[10005],ans[10005],tmp[10005];
bool cmp(minister x,minister y){
	return x.l*x.r<y.l*y.r;
}
void copy(int x[],int y[]){
	for(int i=0;i<10005;i++)
		x[i]=y[i];
}
bool compare(int x[],int y[]){
	for(int i=10004;i>=0;i--){
		if(x[i]>y[i])
			return true;
		if(x[i]<y[i])
			return false;
	}
	return false;
}
void multiply(int multipler[],int num){
	for(int i=10003;i>=0;i--)
		multipler[i]*=num;
	for(int i=0;i<10004;i++){
		multipler[i+1]+=(multipler[i]/10);
		multipler[i]%=10;
	}
}
void divide(int dividend[],int res[],int num){
	memset(res,0,sizeof(res));
	int x=0;
	for(int i=10004;i>=0;i--){
    	x=x*10+dividend[i];
		res[i]=x/num;
		x%=num;
	}
}
int main(){
	int n;
	cin>>n;
	for(int i=0;i<=n;i++)
		cin>>ministers[i].l>>ministers[i].r;
	sort(ministers+1,ministers+n+1,cmp);
	product[0]=1;
	for(int i=0;i<=n;i++){
		divide(product,tmp,ministers[i].r);
		if(compare(tmp,ans))
			copy(ans,tmp);
		multiply(product,ministers[i].l);
	}
	bool flag=false;
	for(int i=10004;i>=0;i--){
		if(!flag){
			if(ans[i])
				flag=true;
			else
				continue;
		}
		cout<<ans[i]<<' ';
	}
	return 0;
}

施工完毕。

温馨提示:本期的代码都直接提交均无法AC,请不要无脑Ctrl C+Ctrl V

  • 25
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值