贪心算法

防晒

有C头奶牛进行日光浴,第i头奶牛需要minSPF[i]到maxSPF[i]单位强度之间的阳光。
每头奶牛在日光浴前必须涂防晒霜,防晒霜有L种,涂上第i种之后,身体接收到的阳光强度就会稳定为SPF[i],第i种防晒霜有cover[i]瓶。
求最多可以满足多少头奶牛进行日光浴。

先将每头奶牛按照需要的minSPF排序。将防晒霜排序,从后向前遍历,如落在某头奶牛的minSPF、maxSPF区间内,则结果加+1,表示成下图。
在这里插入图片描述

上面的小线段表示的就是每头奶牛的minSPF,maxSPF区间,下面横线的点代表防晒霜的SPF,我们从后向前遍历奶牛,若某个防晒霜落在我们的区间就配对成功,同时有多个防晒霜落入的话,我们选取最大的防晒霜。我们可以利用反证法,来证明正确性。
想增加配对数,我们必然要将当前防晒霜给某一个没有配对的区间。我们可知给后面的区间是不成立的,因为我们是从后向前配对的。所以我们只能给前面的没有配对的空间,我们可以表示成下图。
在这里插入图片描述
若将红色防晒霜给之前没有配对的绿色区间,因为我们每次选择防晒霜的时候都是选择区间内最大的防晒霜,因此我们可知红色防晒霜之后没有落在蓝色区间的了,并且红色防晒霜之前也没有防晒霜,否则绿色区间就不会没有配对了,所以蓝色区间将不能配对。可知并没有改变结果数,所以无论向前还是向后交换最终得到的结果数不会大于我们的方法,得证。

#include <iostream>
#include <map>
#include <vector>
#include <algorithm>

using namespace std;

//cow是奶牛的区间,spf是防晒霜的值和数量
vector<pair<int, int>> cow;
map<int, int> spf;

int main(){
    //c是奶牛数量,l是防晒霜数,minspf和maxspf是区间范围,s是防晒霜的值,cover是每种防晒霜数量,res是配对数
    int c, l, minspf, maxspf, s, cover, res = 0;
    cin >> c >> l;
    while(c--){
        cin >> minspf >> maxspf;
        cow.push_back(pair<int, int>(minspf, maxspf));
    }
    while(l--){
        cin >> s >> cover;
        //因为有可能有重复值输入,要累加到一起。
        spf[s]+=cover;
    }
    //将奶牛排序
    sort(cow.begin(), cow.end());
    
    //从后向前遍历奶牛
    for(int i=cow.size()-1; i>=0; i--){
    	//upper_bound可以找到大于区间右端点的第一个key。
        auto iter = --spf.upper_bound(cow[i].second);
        if(iter->first <= cow[i].second && iter->first >= cow[i].first){
            res++;
            iter -> second --;
            if(!iter->second) spf.erase(iter);
        }
    }
    cout << res;
}
畜栏预定

有N头牛在畜栏中吃草。
每个畜栏在同一时间段只能提供给一头牛吃草,所以可能会需要多个畜栏。
给定N头牛和每头牛开始吃草的时间A以及结束吃草的时间B,每头牛在[A,B]这一时间段内都会一直吃草。
当两头牛的吃草区间存在交集时(包括端点),这两头牛不能被安排在同一个畜栏吃草。
求需要的最小畜栏数目和每头牛对应的畜栏方案。

我们可以把牛的吃草时间看成一段区间,问题就变成求解最少的集合数,使得集合中所有区间无交集。对于某一区间,与之前所有集合中的元素均存在交集,那么我们就要新开辟一个集合。
对于每个区间,我们可以按左端点进行排序,依次将当前区间与之前创立的集合最右端点进行比较,若均存在交集,那么新建一集合。若不存在交集,那么我们可以将区间插入任意一个满足条件的集合,因为我们已经将区间排序,所以无论插入哪个集合对后续区间没有影响。进一步简化比较过程,利用小顶堆动态维护所有集合中的最小右端点,每次直接与堆顶比较即可。

#include <iostream>
#include <queue>
#include <vector>
#include <algorithm>

using namespace std;

typedef pair<int, int> pii;
priority_queue<pii, vector<pii>, greater<pii>> q;
int res[50010];

//储存牛的信息,包括开始时间,结束时间,以及序号
struct cowinfo{
    int l, r, num;
};

vector<cowinfo> cow;

//两头牛的排序函数
int cmp1(const cowinfo a, const cowinfo b){
    if(a.l < b.l) return 1;
    else if(a.l == b.l) return a.r <= b.r;
    else return 0;
}

int main(){
	//n牛的数量,a,b吃草开始和结束时间,index是序号
    int n, a, b, index = 0;
    cin >> n;
    for(int i=0; i<n; i++){
        cin >> a >> b;
        cow.push_back(cowinfo{a, b, index++});
    }
    //将牛的吃草开始时间排序
    sort(cow.begin(), cow.end(), cmp1);
    
    index = 1;
    for (int i=0; i<n; i++){
    	//如果堆为空或者堆顶元素小于当前牛吃草开始时间,那么将当前牛插入堆顶栅栏
        if(q.size() && cow[i].l>q.top().first){
            res[cow[i].num] = q.top().second;
            q.pop();
        }
        //否则新开一个栅栏
        else res[cow[i].num] = index++;
        q.push(pii(cow[i].r, res[cow[i].num]));
    }
    
    cout << index - 1 << '\n';
    for(int i=0; i<n; i++) cout << res[i] << '\n';
    
    return 0;
}
雷达设备

假设海岸是一条无限长的直线,陆地位于海岸的一侧,海洋位于另外一侧。
每个小岛都位于海洋一侧的某个点上。
雷达装置均位于海岸线上,且雷达的监测范围为d,当小岛与某雷达的距离不超过d时,该小岛可以被雷达覆盖。
我们使用笛卡尔坐标系,定义海岸线为x轴,海的一侧在x轴上方,陆地一侧在x轴下方。
现在给出每个小岛的具体坐标以及雷达的检测范围,请你求出能够使所有小岛都被雷达覆盖所需的最小雷达数目。

我们可以把岛屿可检测范围映射到海岸线上的一个区间,如下图:
在这里插入图片描述
因此,问题可以转换为若干个区间,找到最少的点,使得所有区间内都有点被包含。
我们可以将区间右端点排序,选取第一个区间的右端点建立雷达,看后续区间有多少可以包含这一点,直到该点不能被某个区间包含,我们则再以这一区间右端点建立雷达,以此类推。

#include <iostream>
#include <algorithm>
#include <math.h>

using namespace std;

typedef pair<double, double> pdd;

vector<pdd> land;
int n, d, x, y;
double l, r, eps = 1e-6;

//求解点到线的映射
pdd get_range(){
    l = x - sqrt(d*d - y*y);
    r = x + sqrt(d*d - y*y);
    return pdd(r, l);
}


int main(){
	//n表示岛屿数,d表示雷达监测范围
    cin >> n >> d;
    for(int i=0; i<n; i++){
    	//x,y是岛屿坐标
        cin >> x >> y;
        //如果y大于d说明该岛屿检测不到,不存在方案数
        if(y>d){
            cout << -1;
            return 0;
        }
        land.push_back(get_range());
    }
    sort(land.begin(), land.end());
    
    int res=0;
    double pos;
    for(int i=0; i<n; i++){
    	//如果当前雷达不在当前区间范围内,那么新建一个雷达
        if(i==0 || pos < land[i].second - eps) {
            pos = land[i].first;
            res++;
        }
    }
    cout << res;
    
    return 0;
}
国王游戏

恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏。
首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。
然后,让这 n 位大臣排成一排,国王站在队伍的最前面。
排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:
排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。
国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。
注意,国王的位置始终在队伍的最前面。

我们先假设已经排好序,那么对于第i个大臣和第i+1个大臣,交换前后的结果可以表示成下表:

颠倒前颠倒后
i位置 L 1 L 2 ⋅ ⋅ ⋅ L i − 1 / R i L_{1}L_{2}···L_{i-1}/R_{i} L1L2Li1/Ri L 1 L 2 ⋅ ⋅ ⋅ L i − 1 / R i + 1 L_{1}L_{2}···L_{i-1}/R_{i+1} L1L2Li1/Ri+1
i+1位置 L 1 L 2 ⋅ ⋅ ⋅ L i − 1 L i / R i + 1 L_{1}L_{2}···L_{i-1}L_{i}/R_{i+1} L1L2Li1Li/Ri+1 L 1 L 2 ⋅ ⋅ ⋅ L i − 1 L i + 1 / R i L_{1}L_{2}···L_{i-1}L_{i+1}/R_{i} L1L2Li1Li+1/Ri

其中 L i L_{i} Li代表第i个大臣左手的数字, R i R_{i} Ri同理。进一步化简。

颠倒前颠倒后
i位置 R i + 1 R_{i+1} Ri+1 R i R_{i} Ri
i+1位置 L i R i L_{i}R_{i} LiRi L i + 1 R i + 1 L_{i+1}R_{i+1} Li+1Ri+1

我们可知,若 L i + 1 R i + 1 > L i R i L_{i+1}R_{i+1}>L_{i}R_{i} Li+1Ri+1>LiRi,则颠倒后的结果一定大于等于颠倒前的结果,所以我们可以按照每位大臣的左右手乘积之和排序。代码如下,因为涉及到累乘,所以要用高精度乘除法。

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

//n代表大臣的个数,a,b代表每位大臣的左右手数字,sum是累乘结果,res是最终答案,minister是大臣左右手和两者乘积的信息
int n, a, b;
vector<int> sum, res;
struct minister{
    int l, r, multi;
}nums[1010];

//大臣比较函数,以乘积排序
int cmp(minister& x, minister& y){
    if(x.multi < y.multi) return 1;
    else return 0;
}

//高精度乘法
void multiply(int num){
    //flag是进位
    int flag = 0;
    //模拟乘法,从低位开始
    for(int i=sum.size()-1; i>=0; i--){
        flag += num * sum[i];
        sum[i] = flag % 10;
        flag /= 10;
    }
    //如果最终还有进位,就添加到数组前
    while(flag){
        sum.insert(sum.begin(), flag % 10);
        flag /= 10;
    }
}

//高精度除法
vector<int> devision(int num){
	//ans是结果,flag是进位,signal表示当前是否已经开始有非0结果
    vector<int> ans;
    int flag = 0;
    bool signal = false;
    for(int i=0; i<sum.size(); i++){
    	//如果当前结果可以除num非0,则保存整除结果,并进位,
    	//如果除num结果是0,并且已经开始有非0结果,该位置0
        flag = flag * 10 + sum[i];
        if(flag / num){
            ans.push_back(flag / num);
            flag %= num;
            signal = true;
        }
        else if(signal) ans.push_back(0);
    }
    return ans;
}

//vector比较,先比较长度,在用vector自带比较
vector<int> compare(vector<int>& ans){
    if(ans.size() < res.size()) return res;
    else if(ans.size() > res.size()) return ans;
    else{
        if(ans > res) return ans;
        else return res;
    }
}

int main(){
    cin >> n;
    for(int i=0; i<=n; i++){
        cin >> nums[i].l >> nums[i].r;
        nums[i].multi = nums[i].r * nums[i].l;
    }
    //排序,除国王第一位
    sort(nums+1, nums+n+1, cmp);
    
    for(int i=0; i<=n; i++){
		//形成数组
        if(i==0){
            int temp = nums[0].l;
            while(temp){
                sum.insert(sum.begin(), temp % 10);
                temp /= 10;
            }
        }
        else{
        	//除法
            vector<int> ans = devision(nums[i].r);
            //比较
            res = compare(ans);
            //累乘
            multiply(nums[i].l);
        }
    }
    //输出结果
    for(int i=0; i<res.size(); i++) cout << res[i];
    
    return 0;
}
给树染色

一颗树有 n 个节点,这些节点被标号为:1,2,3…n,每个节点 i 都有一个权值 A[i]。
现在要把这棵树的节点全部染色,染色的规则是:
根节点R可以随时被染色;对于其他节点,在被染色之前它的父亲节点必须已经染上了色。
每次染色的代价为T*A[i],其中T代表当前是第几次染色。
求把这棵树染色的最小总代价。

对于两个节点, a > b a>b a>b,那么理所当然我们要优先染a,其次b。若是a还有个父节点p需要在a之前染色,那么a、b、p染色顺序是什么呢?我们假设先染p,接下来因为 a > b a>b a>b,则染a,最后是b,如此得到的代价就是 1 p + 2 a + 3 b 1p+2a+3b 1p+2a+3b。如果我们先染b,再p和a,得到的代价就是 1 b + 2 p + 3 a 1b+2p+3a 1b+2p+3a。先染b的前提就是 1 p + 2 a + 3 b > 1 b + 2 p + 3 a 1p+2a+3b>1b+2p+3a 1p+2a+3b>1b+2p+3a,即 b > ( a + p ) / 2 b>(a+p)/2 b>(a+p)/2,将结论进一步扩展,对于两组节点,我们应该先染平均值大的那组节点。
因此,我们的解决办法就是每次选取平均值大的一组(或者一个)节点,将其放到其父节点之后,和父节点形成一组,继续选取。最终算法复杂度就是 O ( n 2 ) O(n^{2}) O(n2)。代码如下:

#include <iostream>
#include <algorithm>

using namespace std;

//节点信息,s表示组内有多少节点,v表示节点代价,p存储的是父节点序号,avg是组内节点代价均值。
struct treenode{
    int s, v, p;
    double avg;
}node[1010];

//n表示节点个数,r是根节点序号,b的父节点是a。
int a, b, n, r;

//找到当前均值最大的节点组,根节点除外。
int find(){
    int res;
    double avg = 0;
    for(int i=1; i<=n; i++){
        if(i != r && node[i].avg > avg){
            avg = node[i].avg;
            res = i;
        }
    }
    return res;
}

int main(){
    int ans = 0;
    cin >> n >> r;
    for(int i=1; i<=n; i++){
        cin >> node[i].v;
        node[i].avg = node[i].v;
        node[i].s = 1;
        //ans是最终结果
        ans += node[i].v;
    }
    
    for(int i = 0; i<n-1; i++){
        cin >> a >> b;
        node[b].p = a;
    }
    
    //循环n-1次,因为根节点不用考虑,一定排在首位
    for(int i=0; i<n-1; i++){
    	//找到当前均值最大的节点组的根节点序号
        int num = find();
        //找到该节点组跟节点的父节点
        int parent = node[num].p;
        //将该节点组添加到父节点组的末尾,则节点组的代价就会推迟父节点组内的节点个数时间
        ans += node[parent].s * node[num].v;
        //找到所有父节点是当前节点组跟节点的节点,更换父节点
        for(int j=1; j<=n; j++)
            if(node[j].p == num)
                node[j].p = parent;
        //跟新父节点信息
        node[parent].v += node[num].v;
        node[num].avg = -1;
        node[parent].s += node[num].s;
        node[parent].avg = (double) node[parent].v / node[parent].s;
    }
    
    cout << ans;
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值