总是做出当前最好的选择
问题分解为多个子问题(阶段)
子问题就局部最优解
局部最优解组合成原问题的解
简单贪心 区间贪心
例7.2 FatMouse' Trade 百分比任意购买 所以不是dp
例7.3 Senior's Gun
ECNU 1045 箱子打包
https://acm.ecnu.edu.cn/problem/1045/
题目描述
有一组 1 维的物品需要打包进一批同样的箱子中。所有的箱子有完全一样的长度 l 以及每一个物品长度 li <= l. 我们要找到一个最小的数字 q, 使得 :
(1) 每个箱子最多包含两个物品
(2) 每个物品要被包含在 q 个箱子中的一个中
(3) 在箱子中物品的总长度不能超过 l
你的任务是,给定 n 个整数,l,l1,l2…ln, 计算最小的箱子总数 q.
输入格式
第一行包含一个数字 n(1 <= n <= 10^5), 表示有几个物品。第二行包含一个整数表示了箱子的长度 l (l <= 10000). 接下去的 n 行是表示了每个物品的长度。
输出格式
输出一个整数,表示能够包含所有物品的最小的箱子总数。
样例
input
10 80 70 15 30 35 10 80 20 35 10 30
output
6
策略:每次选最小的和最大的,如果加起来溢出则只存最大的
POJ 2456 Aggreseive cows
http://poj.org/problem?id=2456
题意:
有n个牛栏,选m个放进牛,相当于一条线段上有n个点,选取m个点,使得相邻点之间的最小距离值最大
策略:最小的距离最大化-判定性问题+二分策略
判断d是否可行,之后再对d二分,贪一点,能满足就再大一点,不满足就再小一点
int arr[maxn];//n个点的坐标
sort(arr,arr + n);
bool Judge(int n,int m,int distance){
int number = 1;
int current = arr[0];
for(int i = 1;i < n;++i){
if(arr[i] - current >= distance) number++,current = arr[i];
if(number >= m) return true;
}
return false;
}
int binarySearch(int n,int m){
int left = 1;
int right = arr[n - 1] - arr[0];
while(left <= right){
int mid = (left + right) / 2;
if(Judge(n,m,mid)) left = mid + 1;
else right = mid - 1;
}
return right;
}
POJ 3104 Drying
http://poj.org/problem?id=3104
题目大意:
Jane洗完了一些衣服,每件衣服有一定的水量,现在她有一台烘干机(每次只能烘一件),烘干机每分钟可以烘干k数量的水,而风干每分钟只能蒸发1数量的水。求如何使用烘干机,使得这些衣服全部变干的时间最短,烘干机每次使用要是整数分钟。
策略:判定+二分
每件衣服所使用烘干机的时间X要满足
kX + (time - X) >= water[i]
X = ceil((water[i] - time) / (k - 1));
int water[maxn];
sort(water,water + n);
if(k == 1) return water[n - 1];
bool Judge(int n,int k,int time){
int sum = 0;
for(int i = 0;i < n;++i){
if(water[i] > time) sum += ceil((water[i] - time) / (k - 1));//ceil <cmath>
if(sum > time) return false;
}
return true;
}
int binarySerach(int n,int k){
int left = 1;
int right = water[n - 1];
while(left <= right){
int mid = (left + right) / 2;
if(Judge(n,k,mid)) right = mid - 1;
else left = mid + 1;
}
return left;
}
HDU 2037 今年暑假不AC
http://acm.hdu.edu.cn/showproblem.php?pid=2037
在一天的时间里,尽可能的看最多的完整节目
抽象为:多个区间选取尽最多的不相交区间
策略:将右端点从小到大排序,每次选择右端点小的(与current不相交的情况下)
sort(arr, arr + n, Compare);
int currentTime = 0;
int answer = 0;
for (int i = 0; i < n; ++i) {
if (currentTime <= arr[i].startTime){
currentTime = arr[i].endTime;
answer++;
}
}
POJ 1328 Radar Installation
http://poj.org/problem?id=1328
每个区间中至少一个点求最少的点的问题
策略:左端点从小到大排序,选择左端点最小的区间
int ans = 1;
int current = arr[0].right;
for(int i = 1;i < n;++i){
if(arr[i].left <= current) current = min(current,arr[i].right);
else ans++,current = arr[i].right;
}
代理服务器
http://t.cn/E9emuS9
题目描述
使用代理服务器能够在一定程度上隐藏客户端信息,从而保护用户在互联网上的隐私。我们知道n个代理服务器的IP地址,现在要用它们去访问m个服务器。这 m 个服务器的 IP 地址和访问顺序也已经给出。系统在同一时刻只能使用一个代理服务器,并要求不能用代理服务器去访问和它 IP地址相同的服务器(不然客户端信息很有可能就会被泄露)。在这样的条件下,找到一种使用代理服务器的方案,使得代理服务器切换的次数尽可能得少。
输入描述:
每个测试数据包括 n + m + 2 行。
第 1 行只包含一个整数 n,表示代理服务器的个数。
第 2行至第n + 1行每行是一个字符串,表示代理服务器的 IP地址。这n个 IP地址两两不相同。
第 n + 2 行只包含一个整数 m,表示要访问的服务器的个数。
第 n + 3 行至第 n + m + 2 行每行是一个字符串,表示要访问的服务器的 IP 地址,按照访问的顺序给出。
每个字符串都是合法的IP地址,形式为“xxx.yyy.zzz.www”,其中任何一部分均是0–255之间的整数。输入数据的任何一行都不包含空格字符。其中,1<=n<=1000,1<=m<=5000。
输出描述:
可能有多组测试数据,对于每组输入数据, 输出数据只有一行,包含一个整数s,表示按照要求访问服务器的过程中切换代理服务器的最少次数。第一次使用的代理服务器不计入切换次数中。若没有符合要求的安排方式,则输出-1。
示例1
输入
3
166.111.4.100
162.105.131.113
202.112.128.69
6
72.14.235.104
166.111.4.100
207.46.19.190
202.112.128.69
162.105.131.113
118.214.226.52
输出
1
贪心策略:每次都选择当前能代理最多服务器的代理服务器。
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1e3 + 10;
const int maxm = 5e3 + 10;
string server[maxn];
string ip[maxm];
struct record {
int occur;
int index;
record(int oc, int in) :occur(oc), index(in) {}
bool operator<(record c) const {
return occur < c.occur;
}
};
int main() {
int n, m;
while (cin >> n) {
for (int i = 0; i < n; ++i) cin >> server[i];
cin >> m;
for (int i = 0; i < m; ++i) cin >> ip[i];
if(n == 1){
bool flag = false;
for(int i = 0;i < m;++i){
if(server[0] == ip[i]){
cout << -1 << endl;
flag = true;
break;
}
}
if(flag) continue;
}
int index = 0;
record maximum(-1, -1);
int current = -1;
int ans = 0;
while (index < m) {
int count = 0;
for (int i = 0; i < n; ++i) {
if (current == i) continue;
for (int j = index; j < m; ++j) {
if (server[i] == ip[j]) {
count++;
maximum = max(maximum, record(j, i));
break;
}
}
}
if (count < n - 1) break;
current = maximum.index;
index = maximum.occur + 1;
ans++;
}
cout << ans << endl;
}
}
区间覆盖
问题描述
给定N个闭区间[a i,b i ]以及一个线段区间[s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。输出最少区间数,如果无法完全覆盖则输出-1。
问题分析
将所有的区间 按照左端点值a i 的递增顺序排序。
贪心策略: 每次选取据起点s最长的区间,并且通过此区间来更新新的起点。
int s,t,n;
pair<int,int> region[n];
sort(region,region + n);
int end = -INF,ans = 0;
for(int i = 0;i < n;++i){
if(region[i].second <= s) continue;//右端点小于s的直接忽略
if(region[i].first <= s) ans++;//左端点小于s,合法
else break;//左端点大于s,无解
while(i < n && region[i].first <= s){//从所有合法的中选择右端点最大的,更新为end
end = max(end,region[i].second);
i++;
}
i--;
if(end >= t) break;//如果end >= t 问题结束
s = end;//否则更新s = end;
}
区间分组
问题描述
给定N个闭区间[a i,b i],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。
输出最小组数。
问题分析
将所有区间按照a i递增的顺序排序。
我们需要考虑的是已有的所有区间组中是否有区间与当前区间有交集,即我们可以保存所有区间组中最右区间的右端点值,若是值最小的区间与其无交点则可以存放如此组中,同时更新组的值。
但是,每次查找最小值的时间复杂度为O(n),制约了整个算法时间复杂度,故我们可以通过小根堆在O(logn)的时间内更新堆并在常数时间内获取最小值。
贪心策略: 通过堆寻找所有区间组中的右端点最小值。
#define x first
#define y second
typedef pair<int, int> PII;
const int N = 1e5+10;
PII reg[N];
sort(reg, reg + n);
priority_queue<int, vector<int>, greater<int> > heap;//小根堆中存放的各个集合的最右边的值
for(int i = 0; i < n; ++i) { //若左端点与堆顶冲突,则新开一个区间,否则更新堆顶
if(heap.empty() || heap.top() >= reg[i].x) heap.push(reg[i].y); //创建新的区间
else{ //更新堆顶元素
heap.pop();
heap.push(reg[i].y);
}
}
贪心之哈夫曼
一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的 金条,不管切成长度多大的两半,都要花费20个铜板。一群人想整分整块金 条,怎么分最省铜板?
例如,给定数组{10,20,30},代表一共三个人,整块金条长度为10+20+30=60. 金条要分成10,20,30三个部分。 如果, 先把长度60的金条分成10和50,花费60 再把长度50的金条分成20和30,花费50 一共花费110铜板。但是如果, 先把长度60的金条分成30和30,花费60 再把长度30金条分成10和20,花费30 一共花费90铜板。输入一个数组,返回分割的最小代价。
思想:
经典的贪心算法,用小根堆来做:
首先根据给定的数组构造小根堆,每次poll出两个数据,然后将两数据之和相加后放回小根堆,整个过程中所有非叶子节点之和就是所求的最小代价,这也是一个标准的哈夫曼编码问题。