《算法竞赛进阶指南》0x07贪心

贪心

贪心是一种每次在决策时采用当前一一下最优策略的算法,使用贪心算法要求问题的整体最优性由局部最优性导出。贪心算法的正确性需要证明,常用手法如下。
1.微扰(邻项交换)
证明在任意局面下,任何对局部最优策略的微小改变都会造成整体结果变差。经常用于以“排序”为贪心策略的证明。
2.决策包容性
证明在任意局面下,做出局部最优策略以后,在问题状态空间的可大集合包含了做出其他任何决策后的可达集合。换言之,这个局部最优策略提供的可能性包含了其他所有决策提供的可能性。
3.决策范围扩展
有时候不容易直接证出局部最优策略的正确性。此时可以往后多扩展出来一步,有助于我们对局部最优决策进行验证(验证时可以综合其他手法)。
4反证法
5.数学归纳法

例题
acwing1055.股票买卖Ⅱ
每一天只有买入,卖出或不操作三种策略。我们只需要往后多看一天,就知道当前要怎么操作了。
1.当前持有股票的话,如果下一天还涨,那么就继续持有,否则卖出。
2.当前不持有股票的话,如果下一天还涨,那么买入,否则继续保持空仓。

a n s = ∑ 1 ≤ i < n m a x ( p r i c e s [ i + 1 ] − p r i c e s [ i ] , 0 ) ans=\sum\limits_{1\leq i <n}max(prices[i+1]-prices[i],0) ans=1i<nmax(prices[i+1]prices[i],0)

#include<iostream>
using namespace std;
#define MAX_N 100000
int arr[MAX_N+5];
int n,sum=0;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    cin>>arr[i];
    for(int i=1;i<n;i++)
    if(arr[i+1]>arr[i])
    sum+=arr[i+1]-arr[i];
    cout<<sum;
    return 0;
}

acwing110.防晒

按照minSPF递减的顺序把奶牛排序。
对于每头奶牛寻找当前奶牛能用的SPF值最大的防晒霜。
证明:
因为奶牛按照minSPF排序,对于当前奶牛可用的任意两瓶防晒霜x,y,如果SPF[x]<SPF[y],那么后面的奶牛只可能出现,“x,y都能用”,“x,y都不能用”,“只有x能用”这三种情况,因此x的适用范围更广,当前奶牛选择y更优,因为把x留给后面的奶牛,未来的可达状态包含了选择x时未来可达状态。
另外,每头奶牛对答案的贡献最多是1,即使让当前奶牛放弃日光浴留下防晒霜给后面的奶牛,答案也不会更大。

#include<iostream>
#include<algorithm>
using namespace std;
int c,l;
pair<int,int>cow[2505];
pair<int,int> shai[2505];
bool cmp(pair<int,int>a,pair<int,int>b)
{
    return a.first>b.first;
}
int main()
{
    cin>>c>>l;
    for(int i=1;i<=c;i++)
    cin>>cow[i].first>>cow[i].second;
    for(int i=1;i<=l;i++)
    cin>>shai[i].first>>shai[i].second;
    sort(cow+1,cow+1+c,cmp);
    sort(shai+1,shai+1+l,cmp);
    int ans=0;
    for(int i=1;i<=c;i++)
    {
    	for(int j=1;j<=l;j++)
    	{
    		if(shai[j].first<cow[i].first)break;
    		if(shai[j].first<=cow[i].second&&shai[j].second>0)
    		{
    			ans+=1;
    			shai[j].second-=1;
    			break;
			}
		}
	}
	cout<<ans;
    return 0;
}

acwing111.畜栏预定

按照吃草时间先后排序,对于每一头牛找可以供其吃草的畜栏,没有的话加一个新的。
证明:如果按照当前策略的畜栏数是m,假设存在一个更小的n满足条件,那么在当前策略下,匹配到的畜栏数为n将要扩展第n+1个时,此时前n个畜栏里面最后一头奶牛的结束吃草时间一定比当前奶牛的开始时间要晚,也正因此一定需要扩展第n+1个畜栏,因此假设不成立。

#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
#define MAX_N 50000
int n;
struct Data{
	int l,r,id;
}cow[MAX_N+5];
int ind[MAX_N+5];
set<pair<int,int>>s;
bool cmp(Data a,Data b)
{
	return a.l<b.l;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
    	cin>>cow[i].l>>cow[i].r;
    	cow[i].id=i;
	}
    sort(cow+1,cow+1+n,cmp);
    s.insert({0,1});
    int cnt=1;
    for(int i=1;i<=n;i++)
    {
        if(cow[i].l>s.begin()->first)
        {
            int id=s.begin()->second;
            s.erase(s.begin());
            s.insert({cow[i].r,id});
            ind[cow[i].id]=id;
        }
        else
        {
            s.insert({cow[i].r,++cnt});
            ind[cow[i].id]=cnt;
        }
    }
    cout<<cnt<<endl;
    for(int i=1;i<=n;i++)
    {
        cout<<ind[i]<<endl;
    }
    return 0;
}

acwing112.雷达设备

首先将问题进行转化,将每一个小岛转化为海岸线上的一段区间,首先置pos为负无穷。
将每一个区间按照左端点大小从小到大排序,依次考虑每个区间,如果当前区间的左端点大于pos,那么增加一台雷达,并且pos的值置为当前雷达右端点的值。
证明:决策包容性
对于每一个区间l[i]-r[i],有两种选择
1.使用已有的设备管辖他
2.新建一台监控设备管辖他
如果策略1可行,则不会选择2。
选择1之后,未来可以在任意位置建立新的设备,而选择2之后,设备的位置会被局限于l[i]-r[i]的位置,因此第1项选择包括了第2项选择未来能够到达的状态。
其次,我们把上一台设备调整到min(r[i],pos)的位置,也就是在能管辖到i的前提下尽可能往后放,“尽可能往后放”未来的可达状态也包含了“放在更靠前的位置”未来的可达状态。最后,因为已经按照了左端区间排序,所以不会影响到前面的区间,证毕。

#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
#define MAX_N 50000
int n;
struct Data{
	int l,r,id;
}cow[MAX_N+5];
int ind[MAX_N+5];
set<pair<int,int>>s;
bool cmp(Data a,Data b)
{
	return a.l<b.l;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
    	cin>>cow[i].l>>cow[i].r;
    	cow[i].id=i;
	}
    sort(cow+1,cow+1+n,cmp);
    s.insert({0,1});
    int cnt=1;
    for(int i=1;i<=n;i++)
    {
        if(cow[i].l>s.begin()->first)
        {
            int id=s.begin()->second;
            s.erase(s.begin());
            s.insert({cow[i].r,id});
            ind[cow[i].id]=id;
        }
        else
        {
            s.insert({cow[i].r,++cnt});
            ind[cow[i].id]=cnt;
        }
    }
    cout<<cnt<<endl;
    for(int i=1;i<=n;i++)
    {
        cout<<ind[i]<<endl;
    }
    return 0;
}

acwing114.国王游戏

按照每个大臣左右手的乘积从小到大排序,就是最优排序方案,可以用微扰证明。
对于任何一种排序,设n名大臣左右手上的数分别是A[1]-A[n]与B[1]-B[n],国王手里分别是A[0]与B[0]。
如果第i+1名大臣能够得到的钱最多,我们期望交换i和i+1的位置后获得更优的答案。在交换前两个大臣获得的奖励是
1 B [ i ] × ∏ j = 0 i − 1 A [ j ] 与 1 B [ i + 1 ] × ∏ j = 0 i A [ j ] \frac{1}{B[i]}\times\prod\limits_{j=0}^{i-1}A[j]与\frac{1}{B[i+1]}\times\prod\limits_{j=0}^{i}A[j] B[i]1×j=0i1A[j]B[i+1]1×j=0iA[j]
交换之后两位大臣的奖励
1 B [ i + 1 ] × ∏ j = 0 i − 1 A [ j ] 与 A [ i + 1 ] B [ i ] × ∏ j = 0 i − 1 A [ j ] \frac{1}{B[i+1]}\times\prod\limits_{j=0}^{i-1}A[j]与\frac{A[i+1]}{B[i]}\times\prod\limits_{j=0}^{i-1}A[j] B[i+1]1×j=0i1A[j]B[i]A[i+1]×j=0i1A[j]

易知交换之后i+1大臣的奖励一定会变少,i大臣的奖励一定会变多,所以我们比较交换前i+1大臣的奖励和交换后i大臣的奖励就能得出交换后时候能获得更优结果。
由于前i-1个大臣奖励不变,所以可以不考虑他们,最后化简后得如果
A [ i ] ∗ B [ i ] ≤ A [ i + 1 ] ∗ B [ i + 1 ] A[i]*B[i]\leq A[i+1]*B[i+1] A[i]B[i]A[i+1]B[i+1]
交换前更优,反之交换后更优,因此按照每个大臣左右手的乘积从小到大排序,就是最优排序方案。

#include<iostream>
#include<vector>
#include<algorithm>
#define MAX 10000
using namespace std;
class BigInt:public vector<int> {
	public:
		BigInt(int x){
			this->push_back(x);
			proccess_digit();
			return ;
		}
		void proccess_digit()
		{
			for(int i=0;i<size();i++)
			{
				if(at(i)<10)continue;
				if(i==size()-1)this->push_back(0);
				at(i+1)+=at(i)/10;
				at(i)%=10;
			}
			while(size()>1&&at(size()-1)==0)this->pop_back();
			return ;
		}
		void operator*=(int x)
		{
			for(int i=0;i<size();i++)at(i)*=x;
			proccess_digit();
			return ;
		}
		bool operator>(const BigInt&a)const{
			if(size()!=a.size())return size()>a.size();
			for(int i=size()-1;i>=0;i--)
			{
				if(at(i)!=a[i])return at(i)>a[i];
			}
			return false;
		}
		BigInt operator/(int x)
		{
			int temp=0;
			BigInt ret(*this);
			for(int i=size()-1;i>=0;i--)
			{
				temp=temp*10+at(i);
				ret[i]=temp/x;
				temp%=x;
			}
			ret.proccess_digit();
			return ret;
		}
	};
		ostream&operator<<(ostream&out,const BigInt&a)
		{
			for(int i=a.size()-1;i>=0;i--)out<<a[i];
			return out;
		}
int main()
	{
	int n;
	int l[MAX+5];
	int r[MAX+5];
	int ind[MAX+5];
	cin>>n;
	for(int i=0;i<=n;i++)cin>>l[i]>>r[i],ind[i]=i;
	sort(ind+1,ind+n+1,[&](int i,int j)->bool{
		return l[i]*r[i]<l[j]*r[j];
	});
	BigInt lc=l[0],ans=l[0]/r[ind[1]];
	for(int i=2;i<=n;i++)
		{
		lc*=l[ind[i-1]];
		if(lc/r[ind[i]]>ans)ans=lc/r[ind[i]];
	}
	cout<<ans;
	return 0;
}
  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值