算法进阶指南0x 贪心

防晒

有C头奶牛进行日光浴,第i头奶牛需要minSPF[i]到maxSPF[i]单位强度之间的阳光。

每头奶牛在日光浴前必须涂防晒霜,防晒霜有L种,涂上第i种之后,身体接收到的阳光强度就会稳定为SPF[i],第i种防晒霜有cover[i]瓶。

求最多可以满足多少头奶牛进行日光浴。

输入格式
第一行输入整数C和L。

接下来的C行,按次序每行输入一头牛的minSPF和maxSPF值,即第i行输入minSPF[i]和maxSPF[i]。

再接下来的L行,按次序每行输入一种防晒霜的SPF和cover值,即第i行输入SPF[i]和cover[i]。

每行的数据之间用空格隔开。

输出格式
输出一个整数,代表最多可以满足奶牛日光浴的奶牛数目。

数据范围
1≤C,L≤2500,
1≤minSPF≤maxSPF≤1000,
1≤SPF≤1000
输入样例:
3 2
3 10
2 5
1 5
6 2
4 1
输出样例:
2

贪心决策:将每头牛按MINspf从大到小排序,每头牛选刚好满足条件且最大的SPF,证明请参考lyd大佬。。。。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>

#define pii pair<int,int>

using namespace std;

const int N=2510;

map<int,int> mp;
pii cows[N];
int n,m,res;

int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    
    scanf("%d%d",&n,&m);

    for(int i=1;i<=n;i++)
        scanf("%d%d",&cows[i].first,&cows[i].second);

    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        mp[x]+=y;
    }

    sort(cows+1,cows+n+1);

    for(int i=n;i>=1;i--)
    {
        auto it=mp.upper_bound(cows[i].second);
        it--;
        if(it->first>=cows[i].first&&it->first<=cows[i].second)
        {
            res++;
            if(--(it->second)==0)
                mp.erase(it);
        }
    }
    cout<<res<<endl;

    return 0;
}

畜栏预定

有N头牛在畜栏中吃草。

每个畜栏在同一时间段只能提供给一头牛吃草,所以可能会需要多个畜栏。

给定N头牛和每头牛开始吃草的时间A以及结束吃草的时间B,每头牛在[A,B]这一时间段内都会一直吃草。

当两头牛的吃草区间存在交集时(包括端点),这两头牛不能被安排在同一个畜栏吃草。

求需要的最小畜栏数目和每头牛对应的畜栏方案。

输入格式
第1行:输入一个整数N。

第2…N+1行:第i+1行输入第i头牛的开始吃草时间A以及结束吃草时间B,数之间用空格隔开。

输出格式
第1行:输入一个整数,代表所需最小畜栏数。

第2…N+1行:第i+1行输入第i头牛被安排到的畜栏编号,编号是从1开始的 连续 整数,只要方案合法即可。

数据范围
1≤N≤50000,
1≤A,B≤1000000
输入样例:
5
1 10
2 4
3 6
5 8
4 7
输出样例:
4
1
2
3
2
4

贪心策略,按左端点排序,一个牛可以放进任意一个可行的畜栏里面去,我们可以用堆优化一下,这样每次拿最小的比就行了。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>

#define pii pair<int,int>

using namespace std;

const int N=50010;

struct Cows{
    int l,r,num;
};

bool CMP(const Cows &a,const Cows &b)
{
    return a.l<b.l;
}

int n;
Cows cows[N];
int id[N];

int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    
    scanf("%d",&n);

    for(int i=1;i<=n;i++)
        scanf("%d%d",&cows[i].l,&cows[i].r),cows[i].num=i;

    sort(cows+1,cows+n+1,CMP);

    priority_queue<pii,vector<pii>,greater<pii>> heap;

    for(int i=1;i<=n;i++)
    {
        if(heap.empty() || heap.top().first>=cows[i].l)
        {
            heap.push({cows[i].r,heap.size()+1});
            id[cows[i].num]=heap.size();
        }
        else
        {
            auto temp=heap.top();
            heap.pop();
            heap.push({cows[i].r,temp.second});
            id[cows[i].num]=temp.second;
        }
        
    }
    printf("%d\n",heap.size());
    for(int i=1;i<=n;i++)
        printf("%d\n",id[i]);

    return 0;
}

雷达设备

假设海岸是一条无限长的直线,陆地位于海岸的一侧,海洋位于另外一侧。

每个小岛都位于海洋一侧的某个点上。

雷达装置均位于海岸线上,且雷达的监测范围为d,当小岛与某雷达的距离不超过d时,该小岛可以被雷达覆盖。

我们使用笛卡尔坐标系,定义海岸线为x轴,海的一侧在x轴上方,陆地一侧在x轴下方。

现在给出每个小岛的具体坐标以及雷达的检测范围,请你求出能够使所有小岛都被雷达覆盖所需的最小雷达数目。

输入格式
第一行输入两个整数n和d,分别代表小岛数目和雷达检测范围。

接下来n行,每行输入两个整数,分别代表小岛的x,y轴坐标。

同一行数据之间用空格隔开。

输出格式
输出一个整数,代表所需的最小雷达数目,若没有解决方案则所需数目输出“-1”。

数据范围
1≤n≤1000
输入样例:
3 2
1 2
-3 1
2 1
输出样例:
2

这题的思路就是先做一个降维打击,变为一维,然后贪心一下就可以了,把右端点排序就可以了。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>

#define pdd pair<double,double>

using namespace std;

const int N=1010;
const double eps=1e-6,INF=1e10;

pdd point[N];
int n,r;

bool CMP(const pdd &a,const pdd &b)
{
    return a.second<b.second;
}
int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    
    scanf("%d%d",&n,&r);

    bool flag=false;
    for(int i=1;i<=n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        if(abs(y)>r)
        {
            flag=true;
            break;
        }
        double d=sqrt(1.0*r*r-y*y);
        point[i]={x-d,x+d};
    }

    if(flag) puts("-1");
    else 
    {
        sort(point+1,point+n+1,CMP);
        double last=-INF;
        int res=0;
        for(int i=1;i<=n;i++)
        {
            if(point[i].first>last+eps)
            {
                last=point[i].second;
                res++;
            }
        }
        printf("%d\n",res);
    }
    return 0;
}

国王游戏

恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏。

首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。

然后,让这 n 位大臣排成一排,国王站在队伍的最前面。

排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:

排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。

国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。

注意,国王的位置始终在队伍的最前面。

输入格式
第一行包含一个整数 n,表示大臣的人数。

第二行包含两个整数 a 和 b,之间用一个空格隔开,分别表示国王左手和右手上的整数。

接下来 n 行,每行包含两个整数 a 和 b,之间用一个空格隔开,分别表示每个大臣左手和右手上的整数。

输出格式
输出只有一行,包含一个整数,表示重新排列后的队伍中获奖赏最多的大臣所获得的金币数。

数据范围
1≤n≤1000
0<a,b<10000
输入样例:
3
1 1
2 3
7 4
4 6
输出样例:
2

贪心思路:按左手的乘积排序,然后求最大值就行了,给大家写一遍,快乐高精度,高精一时爽~ 。~

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

#define pii pair<int,int>

using namespace std;

const int N=1010;

pii att[N];
int n;

vector<int> mul(vector<int> &a,int b)
{
    vector<int> res;
    int t=0;
    for(int i=0;i<a.size();i++)
    {
        t+=a[i]*b;
        res.push_back(t%10);
        t/=10;
    }
    while(t)
    {
        res.push_back(t%10);
        t/=10;
    }
    return res;
}

vector<int> div(vector<int> &a,int b)
{
    vector<int> res;
    int t=0;
    for(int i=a.size()-1;i>=0;i--)
    {
        t=10*t+a[i];
        res.push_back(t/b);
        t%=b;
    }

    reverse(res.begin(),res.end());
    while(res.size()>1&&res.back()==0) res.pop_back();
    return res;
}

bool compare(const vector<int> &a,const vector<int> &b){
    if(a.size()!=b.size()) return a.size()>b.size();
    return vector<int> (a.rbegin(),a.rend()) > vector<int> (b.rbegin(),b.rend());

}

bool CMP(const pii &a,const pii &b)
{
    return a.first*a.second<b.first*b.second;
}

int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);

    scanf("%d",&n);

    for(int i=0;i<=n;i++)
        scanf("%d%d",&att[i].first,&att[i].second);

    sort(att+1,att+n+1,CMP);

    vector<int> res(1,0);
    vector<int> temp(1,1);

    temp=mul(temp,att[0].first);

    for(int i=1;i<=n;i++)
    {
        vector<int> value=div(temp,att[i].second);
        if(!compare(res,value)) res=value;
        temp=mul(temp,att[i].first);
    }
    reverse(res.begin(),res.end());
    for(auto x:res) cout<<x;
    puts("");

    return 0;
}

给树染色

一颗树有 n 个节点,这些节点被标号为:1,2,3…n,每个节点 i 都有一个权值 A[i]。

现在要把这棵树的节点全部染色,染色的规则是:

根节点R可以随时被染色;对于其他节点,在被染色之前它的父亲节点必须已经染上了色。

每次染色的代价为T*A[i],其中T代表当前是第几次染色。

求把这棵树染色的最小总代价。

输入格式
第一行包含两个整数 n 和 R ,分别代表树的节点数以及根节点的序号。

第二行包含 n 个整数,代表所有节点的权值,第 i 个数即为第 i 个节点的权值 A[i]。

接下来n-1行,每行包含两个整数 a 和 b ,代表两个节点的序号,两节点满足关系: a 节点是 b 节点的父节点。

除根节点外的其他 n-1 个节点的父节点和它们本身会在这 n-1 行中表示出来。

同一行内的数用空格隔开。

输出格式
输出一个整数,代表把这棵树染色的最小总代价。

数据范围
1≤n≤1000,
1≤A[i]≤1000
输入样例:
5 1
1 2 1 2 4
1 2
1 3
2 4
3 5
输出样例:
33

这题的贪心思路就是:染色顺序按每个点及其子节点的平均值进行排序,但我们不会傻呵呵的每次更新都算一下平均值,高斯告诉我们:做任何事都要找窍门,我们不断的归并节点,最后全部转移到根节点上,这样我们就可以省略了求平均值的过程,这个做法为什么是对的呢?请自行上网搜索,主要是不会证明

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N=1010;

struct Node
{
    int w,s,p;
    double avg;
};

Node node[N];
int n,root;

int get(){
    int pos=-1;
    double maxv=-1;
    for(int i=1;i<=n;i++)
    {
        if(i!=root&&maxv<node[i].avg)
        {
            pos=i;
            maxv=node[i].avg;
        }
    }

    return pos;
}
int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    
    scanf("%d%d",&n,&root);

    for(int i=1;i<=n;i++)
    {
        scanf("%d",&node[i].w);
        node[i].avg=node[i].w;
        node[i].s=1;
    }

    for(int i=0;i<n-1;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        node[b].p=a;
    }
    
    int res=0;

    for(int i=0;i<n-1;i++)
    {
        int x=get();
        int fa=node[x].p;
        res+=node[x].w*node[fa].s;
        
        for(int j=1;j<=n;j++)
            if(node[j].p==x)
                node[j].p=fa;
        
        node[x].avg=-1;
        node[fa].w+=node[x].w;
        node[fa].s+=node[x].s;
        node[fa].avg=1.0*node[fa].w/node[fa].s;
    }
    res+=node[root].w;
    printf("%d\n",res);

    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值