贪心法

贪心法 C++

贪心法通过局部最优争取达到全局最优,虽然并不总是能导致全局最优


适用于贪心法问题的特征:
1.最优子结构性质。问题的最优解包含其子问题的最优解;
2.贪心选择性质。问题的最优解可以通过一系列局部最优解来得到。

常见问题分类

1.活动安排问题

HDOJ 2037 今年暑假不AC

解题思路:
考虑下面三种贪心策略:
1.最早开始时间(错误,如果一直不结束,后面的活动无法开始)
2.最早结束时间(正确,早结束早开始)(重点要想到这个)
3.用时最少(错误)


算法步骤如下:
1.把所有活动按照结束时间从早到晚排序
2.从第一个开始遍历,如果下一个活动的开始时间小于当前活动的结束时间,排除。

#include<bits/stdc++.h>
using namespace std;
struct point{
	int start,end;
};
bool func(const point &a1, const point &a2){
	return a1.end<a2.end; //自定义规则:返回true表示a1排在a2前面 
}
int main(){
	int n;
	while(~scanf("%d",&n) && n!=0){
		struct point time[n];
		for(int i=0;i<n;i++)
			scanf("%d %d",&time[i].start,&time[i].end);
		sort(time+0,time+n,func); //对数组中下标为0~(n-1)的按照end时间从小到大排序
		int sign=0;//记录结果
		int e=-1; 
		for(int i=0;i<n;i++)	//关键 
			if(time[i].start>=e){ //当前活动的start大于等于上一个活动的end 
				sign++;
				e = time[i].end; //记录上一个活动的结束时间 
			}
		printf("%d\n",sign); 
	}
	return 0;
}

2.区间覆盖问题

POJ 2376 Cleaning Shifts
Description

John安排他的N(1<=N<=25000)个仆人在畜棚做日常打扫工作。他只想让仆人单独做完一件事。他把一天分成T(1<=T<=1000000)个区间.
每一个仆人只有在一些打扫工作的区间时间可以工作。任何被选中去打扫的仆人将会在他的间隙时间工作。
你的工作就是帮助John安排一些时间区间给仆人,这样I.每一个时间区间至少有一个仆人II.给尽可能少的仆人安排工作。如果不存在这样的安排,输出-1。

Input

第一行:是N和T,空格分开
之后的N行:每一行包含两个数字,表示每一个仆人可以工作的的开始时间,以及在结束时间过后停止工作。

Output

第一行:John使用的最少仆人数量或者-1

Sample Input

3 10
1 7
3 6
6 10

Sample Output

2


解题思路:
贪心思路是尽可能找出工作时间最长的仆人


算法步骤如下:
1.把每个时间段按照左端点递增排序
2.设已覆盖的区间为 [ L, R ],在剩下的仆人中找出所有左端点小于等于R+1且右端点最大的仆人;判断是否可以把这个仆人的时间段加入到覆盖区间里并更新 [L, R]。
3.重复步骤2直到所有区间被覆盖。

#include<iostream>
#include<algorithm>
using namespace std;
const int N=25002;
struct point{
	int start,end;
}time[N];
bool func(const point &a1, const point &a2){
	return a1.start<a2.start; //自定义规则:返回true表示a1排在a2前面 
}
int main(){
	int n,t;
	while(~scanf("%d %d",&n,&t)){
		for(int i=0;i<n;i++)
			scanf("%d %d",&time[i].start,&time[i].end);
		sort(time+0,time+n,func); //对数组中下标为0~(n-1)的按照end时间从小到大排序
		int l=time[0].start,r=time[0].end; //已覆盖区间 [L, R]
		if(l!=1){
			printf("-1\n");
			break;
		}
		
		int i=1,number=1,max=0;
		while(i<n){
			if(r==t) break;
			bool sign=0;
			if(time[i].start<=(r+1)){ //当出现一个点s<(r+1)时 
				while(time[i].start<=(r+1) && i<n){ //找出之后的所有s<(r+1)的点 
					if(max<time[i].end){
						max = time[i].end;	//记录下最长的e 
						sign = 1;	//表示有一个点可以最优扩大覆盖 
					}
					i++;
				}
				if(sign){
					number++;
					r = max; //扩大了覆盖区域 ,更新(l,r) 
				}
			}else break; //中间有一个区域无人能覆盖
		}
		if(r==t) printf("%d\n",number);
		else printf("-1\n");
	}
	return 0;
}

3.最优装载问题

HDOJ 2570 迷瘴
#include<bits/stdc++.h>
using namespace std;
int main(){
	int c;
	scanf("%d",&c);
	while(c--){
		int n,v,w;
		scanf("%d %d %d",&n,&v,&w);
		int num[n];
		for(int i=0;i<n;i++){
			scanf("%d",&num[i]);
		}
		sort(num,num+n);
		
		int number = 0;
		double add = 0,old=0;
		for(int i=0;i<n;i++){
			number++;
			add += num[i];
			if(add/number>w){
				number--;
				break;
			}else old = add;
		}
		if(!number) printf("0 0.00\n");
		else printf("%d %.2f\n",number*v,old/number*0.01);
	}
	return 0;
}

4.多机调度问题

Description

设有n个独立的作业,由m台相同的计算机进行加工处理。作业 i i i所需的处理时间为 t i t_i ti。每个作业可以在任何一台计算机上加工处理,但不能间断、拆分。要求给出一种作业调度方案,在尽可能短的时间内,由m台计算机加工处理完成这n个作业。
求解多机调度的贪心策略是最长处理时间的作业优先,即把处理时间最长的作业分配给最先空闲的计算机。让处理时间长的作业得到优先处理,从而在整体上获得尽可能短的处理时间。
(1)如果n<=m,需要的时间就是n个作业当中最长的处理时间。
(2)如果n>m,首先将n个作业按照处理时间降序排列,然后按顺序把作业分配给空闲的计算机。


但是,这只是近似解,并不是最优解。
举例:
任务:给出处理时间分别为16 14 12 11 10 9 8的作业,交由三台计算机A、B、C处理。
贪心策略:(用时31)

计算机/时间012+2+2+7+8(31)
AA1642A92
BB142B1081
CC12C1197C8结束

最优策略:(用时27)

计算机/时间010+4+2+3+8(27)
AA1662A11
BB144B1210
CC10C953C8结束

应用:Huffman编码

POJ 1521 Entropy
Description

简而言之,输入一个字符串,分别用ASCII编码(每个字符8bit)和Huffman编码,输出编码后的bit长度以及压缩比。

Sample Input

AAAAABCD
THE_CAT_IN_THE_HAT
END

Sample Output

64 13 4.9
144 51 2.8

#include <string>
#include <vector>
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
int main(){
    string s;
    priority_queue<int, vector<int>, greater<int> > Q;//保持最小的永远在前面
    while (getline(std::cin,s) && s!="END"){
        sort(s.begin(), s.end());
        int t=1;
        for(int i=1;i<s.length();i++) //统计每个字符的频数
            if(s[i]!=s[i-1]){
                Q.push(t);
                t=1;
            }else t++;
        Q.push(t);

        if(Q.size() == 1) {
            printf("%d %d 8.0\n", s.length()*8, s.length());
            Q.pop();
            continue;
        }

        int sum=0;
        while (Q.size()>1){ //挑选最小的两个,组成一个节点的两个分支,向上延伸
            int a = Q.top();Q.pop();
            int b = Q.top();Q.pop();
            Q.push(a+b);
            sum += a+b; //重点思考:为什么能直接相加?
                        // 因为上一行,这两个分支的和已经又包括在Q当中了。在之后的每一次向上延伸,都会算一次并纳入总长度当中。
        }
        printf("%d %d %.1f\n",s.length()*8,sum,(double)s.length()*8.0/(double)sum);
        Q.pop();
    }
    return 0;
}

应用:模拟退火

HDOJ 2899 Strange fuction
Description

函数F(x) = 6x^7 +8x^6 +7x^3 +5x^2-y*x (0 <= x <=100)
输入测试案例个数 T(1<=T<=100) 和 y(0 < y <1e10),输出函数的最小值(4位小数)。

Sample Input

2
100
100

Sample Output

-74.4291
-178.8534

模拟退火算法的主要步骤如下:
(1)设置一个初始温度T;
(2)温度下降,状态转移。从当前温度按降温系数下降到下一个温度,在新的温度计算当前状态;
(3)如果温度下降到设定的下限,程序停止。

伪代码
eps = 1e-8; 		//终止温度,接近0,用于控制精度
T = 100;			//初始温度,应该是高温,以100为例
decrease = 0.98;	//降温系数,控制退火的快慢,小于1
g(x);				//温度为x时的评价函数,例如物理意义上的能量,或者本题的函数值
now,next;			//当前温度,新温度
while(T>eps){
	g(now),g(next);
	dE = g(next)-g(next)
	if( dE>=0 ) now=next;	//新状态更好,接受新状态
	else if(exp(dE/T) > rand())	//新状态更差,在一定概率下接受它
		now = next;
	T *= decrease;
}
#include <bits/stdc++.h>
using namespace std;

const double eps = 1e-8; //终止温度
double y;
const double decrease = 0.98; //温度递减系数
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 x = 50;  //x的初始值
    double now = func(x);
    double res = now;
    while (T > eps){
        int f[2] = {1,-1};
        double newx = x + f[rand()%2]*T;    //按概率改变x
        if(0<=newx && newx<=100){
            double next = func(newx);
            res = min(now, next);
            if(now - next > eps){
                x = newx;
                now = next;
            }
        }
        T *= decrease;
    }
    return res;
}

int main(){
    int n;
    scanf("%d",&n);
    while(n--){
        scanf("%lf",&y);
        printf("%.4f\n",solve());
    }
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

正在学C++

2角不嫌多,1角不嫌少

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

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

打赏作者

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

抵扣说明:

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

余额充值