贪心法进阶

贪心算法

Huffman编码

模拟退火

贪心算法是指,在对问题求解时,总是以当前情况为基础作最优选择,而不考虑各种可能的整体情况,它所做出的仅仅是在某种意义上的局部最优解,省去了为找最优解要穷尽所有可能而必须耗费的大量时间,类似数学归纳法,无后效性,在运行过程中没有回溯过程,每一步都是当前的最佳选择。
难点是如何贪心和证明贪心的正确性,即如何用一个小规模的解构造更大规模的解,比赛过程中需要胆大心细地归纳、分析。

  1. 贪心的缺点:
    1) 可能得不到最优解
    2) 可能算不出答案
  2. 用贪心法求解需要满足以下特征:
    1) 最优子结构性质(当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质)
    2) 贪心选择性质
  3. 贪心常见问题
    1) 活动安排问题(区间调度问题)
    2) 区间覆盖问题
    3) 最优装载问题
    4) 多机调度问题
    就活动安排问题来讲:
    【今年暑假不AC】 题目大意:节目总数n,接着输入每个节目的开始时间和结束时间,输出能看的尽量多的节目数目。
  4. 选择贪心策略
  5. 罗列算法步骤
  6. 判断是否能得到全局最优解
  7. 实现代码
【核心代码】
struct  s{
	int b,e;
}z[100];
bool cmp(s a, s b){
	return a.e<b.e;
}
(输入省略)
sort(z,z+n,cmp)
int time=0,count=0;
for(int i=0;i<n;i++){
	if(z[i].b>=time){
		time=z[i].e;[添加链接描述](https://www.icourse163.org/course/PKU-1001894005?tid=1463180448)
		count++;
	}
}
cout<<count<<endl;

知乎贪心算法:
https://zhuanlan.zhihu.com/p/340804779
中国大学MOOC十一周贪心算法:
https://www.icourse163.org/course/PKU-1001894005?tid=1463180448

贪心相关题目:

A - Intervals POJ - 1089
Description

There is given the series of n closed intervals [ai; bi], where i=1,2,…,n. The sum of those intervals may be represented as a sum of closed pairwise non−intersecting intervals. The task is to find such representation with the minimal number of intervals. The intervals of this representation should be written in the output file in acceding order. We say that the intervals [a; b] and [c; d] are in ascending order if, and only if a <= b < c <= d.
Task
Write a program which:
reads from the std input the description of the series of intervals,
computes pairwise non−intersecting intervals satisfying the conditions given above,
writes the computed intervals in ascending order into std output
Input

In the first line of input there is one integer n, 3 <= n <= 50000. This is the number of intervals. In the (i+1)−st line, 1 <= i <= n, there is a description of the interval [ai; bi] in the form of two integers ai and bi separated by a single space, which are respectively the beginning and the end of the interval,1 <= ai <= bi <= 1000000.
Output

The output should contain descriptions of all computed pairwise non−intersecting intervals. In each line should be written a description of one interval. It should be composed of two integers, separated by a single space, the beginning and the end of the interval respectively. The intervals should be written into the output in ascending order.
Sample Input

5
5 6
1 4
10 10
6 9
8 10
Sample Output

1 4
5 10

//题解一:
#include <iostream>
#include <algorithm>
using namespace std;
struct xz {
	int x;
	int z;
} a[100005],b[100005];
bool cmp(xz a,xz b) {
	return a.x<b.x;
}
int main() {
	int n;
	cin>>n;
	for(int i=0; i<n; i++) {
		cin>>a[i].x>>a[i].z;
	}
	sort(a,a+n,cmp);
	int k=0;
	b[k].x=a[0].x;
	b[k].z=a[0].z;
	for(int i=1; i<n; i++) {
		if(a[i].x<=b[k].z) {
			if(b[k].z<a[i].z)
				b[k].z=a[i].z;
		} else {
			k++;
			b[k].x=a[i].x;
			b[k].z=a[i].z;
		}
	}
	for(int i=0; i<=k; i++)
		cout<<b[i].x<<" "<<b[i].z<<endl;
	return 0;
}

//题解二
#include<iostream>
#include<algorithm>
using namespace std;
struct node
{
	int b;
	int e;
}a[50005];	
bool cmp(node x,node y)
{
	return x.b<y.b;
}
int main()
{
	int n;
	cin>>n;
	for(int i=0;i<n;i++)
	{
		cin>>a[i].b>>a[i].e;
	}
	sort(a,a+n,cmp);
	cout<<a[0].b;
	int biao=a[0].e;
	for(int i=1;i<n;i++)
	{
		if(a[i].b<=biao)
		{
			if(a[i].e>biao)
				biao=a[i].e;
		}
		else
		{
			cout<<" "<<biao<<endl<<a[i].b;
			biao=a[i].e;
		}
	}
	cout<<" "<<biao<<endl;
	return 0;
}

B - Radar Installation POJ - 1328
Assume the coasting is an infinite straight line. Land is in one side of coasting, sea in the other. Each small island is a point locating in the sea side. And any radar installation, locating on the coasting, can only cover d distance, so an island in the sea can be covered by a radius installation, if the distance between them is at most d.

We use Cartesian coordinate system, defining the coasting is the x-axis. The sea side is above x-axis, and the land side below. Given the position of each island in the sea, and given the distance of the coverage of the radar installation, your task is to write a program to find the minimal number of radar installations to cover all the islands. Note that the position of an island is represented by its x-y coordinates.
                  Figure A Sample Input of Radar Installations

Figure A Sample Input of Radar Installations

Input
The input consists of several test cases. The first line of each case contains two integers n (1<=n<=1000) and d, where n is the number of islands in the sea and d is the distance of coverage of the radar installation. This is followed by n lines each containing two integers representing the coordinate of the position of each island. Then a blank line follows to separate the cases.

The input is terminated by a line containing pair of zeros
Output
For each test case output one line consisting of the test case number followed by the minimal number of radar installations needed. “-1” installation means no solution for that case.
Sample Input
3 2
1 2
-3 1
2 1

1 2
0 2

0 0
Sample Output
Case 1: 2
Case 2: 1

#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
struct dao
{
	double x;
	double y;
	double lzx;
	double lyx;
}a[1010];
bool cmp(dao a,dao b)
{
	if(a.x!=b.x)
		return a.x<b.x;
	else
		return a.y<b.y;
}
int main()
{
	int n,r,c=0,biao=0;
	while(cin>>n>>r)
	{
		if(n==0&&r==0)
			break;
		c++;
		for(int i=0;i<n;i++)
		{
			cin>>a[i].x>>a[i].y;
			if(a[i].y>r)
				biao=1;
		}
		if(biao==1)
		{
			cout<<"Case "<<c<<": -1"<<endl;
			biao=0;
		}
		else
		{
			int sum=1;
			double y;
			sort(a,a+n,cmp);
			for(int i=0;i<n;i++)
			{
				a[i].lzx=a[i].x-sqrt(r*r-a[i].y*a[i].y);
				a[i].lyx=a[i].x+sqrt(r*r-a[i].y*a[i].y);
			}
			y=a[0].lyx;
			for(int i=1;i<n;i++)
			{
				if(y>a[i].lyx)
					y=a[i].lyx;
				else
				{
					if(a[i].lzx>y)
					{
						y=a[i].lyx;
						sum++;
					}
				}
			}
			cout<<"Case "<<c<<": "<<sum<<endl; 
		}
	}
	return 0;
}

C - Doing Homework again HDU - 1789
Ignatius has just come back school from the 30th ACM/ICPC. Now he has a lot of homework to do. Every teacher gives him a deadline of handing in the homework. If Ignatius hands in the homework after the deadline, the teacher will reduce his score of the final test. And now we assume that doing everyone homework always takes one day. So Ignatius wants you to help him to arrange the order of doing homework to minimize the reduced score.
Input
The input contains several test cases. The first line of the input is a single integer T that is the number of test cases. T test cases follow.
Each test case start with a positive integer N(1<=N<=1000) which indicate the number of homework… Then 2 lines follow. The first line contains N integers that indicate the deadlines of the subjects, and the next line contains N integers that indicate the reduced scores.
Output
For each test case, you should output the smallest total reduced score, one line per test case.
Sample Input
3
3
3 3 3
10 5 1
3
1 3 1
6 2 3
7
1 4 6 4 2 4 3
3 2 1 7 6 5 4
Sample Output
0
3
5

#include <iostream>
#include <algorithm>
using namespace std;
struct xz {
	int d;
	int s;
} a[100005];
bool cmp(xz x,xz y) {
	if(x.s!=y.s)
		return x.s>y.s;
	return x.d<y.d;
}
int main() {
	int t;
	cin>>t;
	while(t--) {
		int n;
		cin>>n;
		int v[100005]= {0};
		int ans=0;
		for(int i=0; i<n; i++)
			cin>>a[i].d;
		for(int i=0; i<n; i++)
			cin>>a[i].s;
		sort(a,a+n,cmp);
		for(int i=0; i<n; i++) {
			int k=a[i].d;
			for( ; k>=1; k--)
				if(v[k]==0) {
					v[k]=1;
					break;
				}
			if(k==0)
				ans+=a[i].s;
			//	cout<<" "<<a[i].s<<" "<<ans<<endl;
		}
		cout<<ans<<endl;
	}
	return 0;
}

D - Moving Tables HDU - 1050
The famous ACM (Advanced Computer Maker) Company has rented a floor of a building whose shape is in the following figure.

在这里插入图片描述

The floor has 200 rooms each on the north side and south side along the corridor. Recently the Company made a plan to reform its system. The reform includes moving a lot of tables between rooms. Because the corridor is narrow and all the tables are big, only one table can pass through the corridor. Some plan is needed to make the moving efficient. The manager figured out the following plan: Moving a table from a room to another room can be done within 10 minutes. When moving a table from room i to room j, the part of the corridor between the front of room i and the front of room j is used. So, during each 10 minutes, several moving between two rooms not sharing the same part of the corridor will be done simultaneously. To make it clear the manager illustrated the possible cases and impossible cases of simultaneous moving.

在这里插入图片描述

For each room, at most one table will be either moved in or moved out. Now, the manager seeks out a method to minimize the time to move all the tables. Your job is to write a program to solve the manager’s problem.
Input
The input consists of T test cases. The number of test cases ) (T is given in the first line of the input. Each test case begins with a line containing an integer N , 1<=N<=200 , that represents the number of tables to move. Each of the following N lines contains two positive integers s and t, representing that a table is to move from room number s to room number t (each room number appears at most once in the N lines). From the N+3-rd line, the remaining test cases are listed in the same manner as above.
Output
The output should contain the minimum time in minutes to complete the moving, one per line.
Sample Input
3
4
10 20
30 40
50 60
70 80
2
1 3
2 200
3
10 100
20 80
30 50
Sample Output
10
20
30

//经过次数最多门口的经过次数*10就是答案 
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int main() {
	int m;
	cin>>m; 
	while(m--) {
		int n,a[201]={0};//用数组标记所经过的门口数 
		                //每输入一组数据 数组初始化为零 
		cin>>n;
		while(n--) {
			int c,b,t=0;
			cin>>c>>b;
			if(c>b) {
			    t=c;
				c=b;
				b=t;
			}
			c=(c+1)/2;// 400个房间,400个门口,两两个是相对的,所以c,b除以2 
			b=(b+1)/2;
			for(int i=c; i<=b; i++) {
				a[i]++;//每组数据从开始到结束所经过的门口数 
			}
		}
		int x=0;//标记最多经过的门口数 
		for(int i=0; i<201; i++) {
			if(x<a[i]) {
				x=a[i];
			}
		}
		cout<<x*10<<endl;
	}
	return 0;
}

E - 饭卡 HDU - 2546
电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额。如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够)。所以大家都希望尽量使卡上的余额最少。
某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。
Input
多组数据。对于每组数据:
第一行为正整数n,表示菜的数量。n<=1000。
第二行包括n个正整数,表示每种菜的价格。价格不超过50。
第三行包括一个正整数m,表示卡上的余额。m<=1000。

n=0表示数据结束。
Output
对于每组输入,输出一行,包含一个整数,表示卡上可能的最小余额。
Sample Input
1
50
5
10
1 2 3 2 1 1 2 3 2 1
50
0
Sample Output
-45
32

#include<algorithm>
#include<iostream>
#include<cstring>
typedef long long ll;
using namespace std;
int dp[1005];
int main() {
	int n,v[1005],m;
	while(cin>>n&&n) 
	{
		memset(dp,0,sizeof(dp));
		for(int i=1; i<=n; i++)
			cin>>v[i];
		sort(v+1,v+n+1);
		cin>>m;
		if(m>=5) 
		{
			for(int i=1; i<n; i++)
				for(int j=m-5; j>=v[i]; j--)
				{
					dp[j]=max(dp[j],dp[j-v[i]]+v[i]);
				}
			int ans;
			ans=m-dp[m-5]-v[n];
			cout<<ans<<endl;
		} 
		else 
		cout<<m<<endl;
	}
return 0;
}

G - Big Barn HDU - 4846
农夫John想在他的正方形农场上建一个正方形谷仓。他不想在他的农场砍伐树木,所以他要为他的谷仓找一个位置,使他在没有树的地方建谷仓。农场被划分为N×N(N≤1000)块。输入给出这些块的一个列表,在有些块内生长着树。请您找出最大的一个不包含任何树的一块正方形场地。谷仓的边必须与水平轴或垂直轴平行。
  例如,下面给出的是农夫John的土地,其中.' 表示在这个块中没有树木,而’表示这个块中有树木:
1 2 3 4 5 6 7 8
1 . . . . . . . .
2 . # . . . # . .
3 . . . . . . . .
4 . . . . . . . .
5 . . . . . . . .
6 . . # . . . . .
7 . . . . . . . .
8 . . . . . . . .
  最大的谷仓是5×5,可修建在网格右下方的两个位置之一。
Input
  有多组输入数据.(少于20组)
对于每一组数据,首先给出两个整数:N(1≤N≤1000),块的数量;和T(1≤T≤10000),有树的块的数量。第2行到第T+1行,每行两个整数,在区间[1, N]中取值,表示有树的一个块的行和列。
Output
  对于每一组数据,单独输出一行表示John的正方形谷仓的最大边长。
Sample Input
8 3
2 2
2 6
6 3
Sample Output
5

#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
int x,y;
int dp[1010][1010];
int map[1010][1010];
int main(){
	int n,m;
	while(cin>>n>>m){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				map[i][j]=1;
			}
		}
		
		while(m--){
		    cin>>x>>y;
		    map[x][y]=0;
	    }
	    memset(dp,0,sizeof(dp));
	    int sum=0;
	    for(int i=1;i<=n;i++){
	    	for(int j=1;j<=n;j++){
	    		if(map[i][j]==1){
	    			dp[i][j]=min(dp[i-1][j-1],min(dp[i-1][j],dp[i][j-1]))+1; 
				}
				sum=max(sum,dp[i][j]);
			}
		}
		cout<<sum<<endl;
	}
	
	return 0; 
} 

Huffman编码
哈夫曼编码是一种结合二叉树与贪心算法的文字、数据编码方式。
步骤:

  1. 对所有字符按出现频次排序
  2. 从出现频次最少的字符开始,用贪心思想安排在二叉树上
    贪心的过程是按出现频次从底层往顶层生成二叉树,每一步都要按照频次重新排列,保证出现频次少的字符放在树的底层,编码更长,出现多的字符被放在最上层,编码更短。
    【POJ1521 Entropy】输入一个字符串,分别用普通ASCII编码(每个字符8bit)和哈夫曼编码,输出编码后的长度,并输出压缩比。
    【输入样例】AAAAABCD
    【输出样例】64 13 4.9
    【部分代码】
string s;
priority_queue < int,vector<int>,greater<int>>Q;
//优先队列,最小的在队首
while(getline(cin,s)&&s!="END"){
//输入字符串 
	int t=1;
	sort(s.begin(),s.end());
	for(int i=1;i<s.length();i++){
//统计字符出现的频次,并放进优先队列
		 if(s[i]!=s[i-1]){
		 	Q.push(t);
		 	t=1;
		 }
		 else
		 	t++;
	}
	Q.push(t);
	int ans=0;
	while(Q.size()>1){
		int a=Q.top;
		Q.pop();
//提取队列中最小的两个
		int b=Q.top;
		Q.pop();
		Q.push(a+b);
		ans+=a+b;
//***直接计算编码总长度 
	}
	Q.pop();
} 
//ans就是编码后的总长度 

代码实现过程中主要有三个难点,其一是二叉树的构造,其二是最小值的挑选,其三是一维的数组如何转换为二叉树的结构,可以对节点进行编号,用编号来访问具有父子关系的结点,重点就是select函数的实现。

模拟退火算法
定义
模拟退火算法以优化问题求解过程与物理退火过程之间的相似性为基础,优化的目标函数相金属的内能,优化问题的自变量合状态空间相金属的内能状态空间,问题的求解过程就是找一个组合状态,使目标函数值最小。利用Metopolis算法并适当地控制温度的下降过程实现模拟退火,从而达到求解全局优化问题的目的。
算法思想

(1) 初始化:初始温度T(充分大),初始解状态S(是算法迭代的起点),每个T值的迭代次数L

(2) 对k=1, …, L做第(3)至第6步:

(3) 产生新解S′

(4) 计算增量ΔT=C(S′)-C(S),其中C(S)为评价函数

(5) 若ΔT<0则接受S′作为新的当前解,否则以概率exp(-ΔT/T)接受S′作为新的当前解.

(6) 如果满足终止条件则输出当前解作为最优解,结束程序。终止条件通常取为连续若干个新解都没有被接受时终止算法。

(7) T逐渐减少,且T->0,然后转第2步。

注意:初始化,产生新解,新解保留,终止条件
模拟退火算法与初始值无关,算法求得的解与初始解状态S(是算法迭代的起点)无关;模拟退火算法具有渐近收敛性,已在理论上被证明是一种以概率l 收敛于全局最优解的全局优化算法;模拟退火算法具有并行性。
分享链接:
https://blog.csdn.net/luolang_103/article/details/79857412

模拟退火算法的主要步骤如下:

(1) 设置一个初始温度T。

(2) 温度下降,状态转移。从当前温度按降温系数下降到下一个温度,在新的温度计当前状态。

(3) 如果温度下降到设定的温度下界,程序停止。
伪代码如下:

eps = 1e-8;// 终止温度,接近0,用于控制温度 
T = 100;// 初始温度,应该是高温,以100度为例 
delta = 0.98;// 降温系数,控制退火的快慢,小于1,以0.98为例 
g(x);// 状态x时的评价函数,例如物理意义上的能量
now, next;//当前状态和新状态 
while(T> eps){//如果温度未降到eps 
	g(next), g(now);//计算能量 
	dE= g(next)-g(now);//能量差 
	if(dE>=0)//新状态更优,接受新状态 
	   now = next;
	else if(exp(dE/T)>rand())//如果新状态更差,在一定概率下接受它,e^(dE/T) 
	   now =next;
	T * =delta;  //降温,模拟退火过程   
}

模拟退火在算法竞赛中的典型应用有函数最值问题、TSP旅行商问题、最小园覆盖、最小球覆盖等。在本书第11.2.2节中给出了用模拟退火求解最小圆覆盖的例子。下面的例子是求函数最值。
Hud 2899“Strange function”
函数F(x)=6x7+8x6+7x3+5x2-yx,其中x的范围是[0,100]
输入y值,输出F(x)的最小值。

#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-8;//终止温度 
double y;
double func(double x) {//计算函数值 
	return 6*pow(x,7.0)+8*pow(x,6.0)+7*pow(x,3.0)+5*pow(x,2.0)-y*x;
}
double solve() {
	double T = 100;//初始温度 
	double delta = 0.98;//降温系数 
	double x= 50.0;//x的初始值 
	double now =func(x);//计算初始函数值 
	double ans = now;//返回值 
	while(T>eps) {//eps是终止温度 
		int f[2] = {1,-1};
		double newx =x+f [rand()%2]*T;//按概率改变x值 ,随T的降温而减少 
		if(newx>=0&&newx<=100) {
			double next =func(newx);
			ans = min(ans,next);
			if(now - next>eps) {//更新x 
				x = newx;
				now = next;
			}
		}
		T*=delta;
	}
	return ans;
}
int main() {
	int cas;
	scanf("%d",&cas);
	while(cas--) {
		scanf("%lf",&y);
		printf("%.4f\n",solve());
	}
	return 0;
}

PS:有些内容摘自网络,如果觉得侵权可删除。这篇文章不是我一个的努力成果,是我和同学共同完成的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值