贪心——一种算法思维

事实上,这个思维我们一直都存在着,这是之前没有被具体化,贪心(greedy)策略只需要我们选择当下最好的决策然后走下去,我们不去考虑当下的决策会对以后产生什么影响。现实中,我们也是这么做的。

贪心算法在有解决最优子结构问题中尤为有效。

最优子结构的意思是问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解,而不会陷入局部最优解。

 贪心问题最最重要的是敢于大胆的猜测,然后对自己的猜测给出有力的证明。

贪心有两种证明方法:反证法和归纳法。

部分背包问题:【深基12.例1】部分背包问题 - 洛谷 

 该部分背包问题,和DP中部分背包问题不同的是,该金币可以随意分割。

我们猜测每次取当下剩余价值比最大的金币,知道背包取满。

我们假设 a < b (价值比)

那我们取相同重量价值比为a 和 b 的金币,很明显最后得到的价值是后者多。显然这很符合我们的预期。

#include
using namespace std;

const int N = 1010;

typedef long long LL;

int n,t;

struct Pirce{
	float m,v;
	bool operator < (const Pirce &N)const{
		return v/m > N.v/N.m;
	}
		
}Pirce[N];

int main()
{
	cin>>n>>t;
	for(int i =0 ;i < n ;i++)
	{
		int m,v;
		cin>>m>>v;
		Pirce[i] = {m,v};
	}
	sort(Pirce,Pirce + n);
	
	float res = 0;
	
	for(int i =0;i Pirce[i].m)
		{
			res += Pirce[i].v;
			t -= Pirce[i].m;	
		}
		else
		{
			res += (float)Pirce[i].v/Pirce[i].m * t;
			break;
		}
	}
	printf("%.2f\n",res);
	
	return 0;
} 

排序不等式:排队接水 - 洛谷

5

1  5  4  3  2  

我们举一个简单的例子:

假设我们每次让接水时间最少的人先接;

 1  2  3  4  5

第一个人不需要等待,第二人需要等第一人接完,也就是一分钟,第三个人需要等前两个人接完也就是三分钟(这其实是一个前缀和),以此类推........

如果该决策不是最优的,我们使用反证法:

现在我们让 3 4 调换 :

 1  2  4  3  5

原来第四个人等待的时间从 6 分钟变成了 7 分钟。

事实证明没有其他决策可以比我们的猜测更优;

然后解释一下代码的做法:

贪心——一种算法思维_第1张图片

 第一个人接水后面所有人都要等上1分钟,第二人接水后面所有人都要等上2分钟,所以........

#include
using namespace std;

const int N = 1000010;
typedef long long LL;
typedef pairPII;

int n;
PII a[N];

int main()
{
	scanf("%d",&n);
	for(int i =1 ; i <= n ;i ++)
	{
		int x;
		scanf("%d",&x);
		a[i] = {x,i};	
	}
	
	sort(a +1,a+n +1);
	
	LL sum  = 0; 
	
	for(int i =1 ;i<= n ;i ++)
	{
		sum += a[i].first *( n - i);
	}
	
	for(int i=1 ; i <=n ; i++)
	{
		printf("%d ",a[i].second);
	}
	puts("");
	printf("%.2lf\n",(double)sum / n);
	
	return 0;
}

区间问题:凌乱的yyy / 线段覆盖 - 洛谷

区间问题往往都和排序相关,要么根据左端点排序,要么根据右端点排序,具体就要根据题目分析了。

我们直接曲线救国,我们分析一下根据左端点排序;

4

0 9

1 2

3 5

4 6

如果根据左端点排序,答案是1,单正确答案是3。

所以我们应该根据右端点排序。

#include
using namespace std;

const int N = 1000010;

typedef long long LL;
typedef pairPII;
 
int n;
PII ti[N];

int main()
{
	scanf("%d",&n);
	for(int i =0 ;i < n ;i++)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		ti[i] = {r,l};
	} 
	
	sort(ti,ti + n);
	
	int end = -1,res = 0;
	for(int i =0 ;i < n ;i ++)
	{
		int l,r;
		r = ti[i].first, l =ti[i].second;
		 
		if(end <= l)
		{
			res++;
			end = r;
		}
	}
	printf("%d\n",res);
	
	return 0;
} 

 哈夫曼树:[NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G - 洛谷

 这个数据结构大家都熟悉;

贪心——一种算法思维_第2张图片

假设最左侧的果子的质量是2,当合并到最后的时候,该果子一共被加了三次,也就是到根结点的距离,那我们可以知道,到根结点距离越大的果子消耗的体力越多,也就是说我们要保证越外侧的结点权重越小,抽象到我们这个题的话,就是说我们要合并当前最小的两堆。

#include 

using namespace std;

int n;

int main()
{
    
    priority_queue,greater> heap;
    
    cin>>n;
    int res= 0;
    for(int i =0 ;i < n ; i++)
    {
        int x;
        scanf("%d", &x);
        heap.push(x);
    }
    
    while(heap.size() > 1)
    {
        int a = heap.top(); heap.pop();
        int b = heap.top(); heap.pop();
        res += a + b;
        
        heap.push(a+b);
    }
    
    cout<

 终于写完了,这些都是贪心比较基础的题目,贪心类的题目最重要的是证明,以上题目的证明都很简单,像类似于国王游戏的题目,真的需要我们大胆假设,一定要去证明,不然很容易写到最后把自己写死,然后还认为自己的做法很正确。

你可能感兴趣的:(算法,贪心算法,动态规划)