Contest1366 - 【初2019级编程社】贪心专项训练(一)

编程社 贪心专项训练

—————-详细题解

考试概况

这里写图片描述

本次考试一共有六道题,当然,从上图中可以发现,我并没有完成1149这道题。当然,对我来说:这一是一道大佬题,需费时间。但固定的比赛时间不允许我们这么做,所以。。。只有那大神 陶~~ 完成了所有题。而这次考试的题如下:
1146 Problem A 【基础算法】删数游戏(版本1)
1147 Problem B 取数游戏 贪心基础题
1148 Problem C 活动选择 贪心基础题
1149 Problem D 【基础算法】雇佣计划 贪心基础题
1469 Problem E 贪心算法训练题三
1471 Problem F 贪心算法专题训练五
在此博客中,我们会完成1146、1147、1148、1469、1471的解答,至于1149的这道题。我们会在第二篇博客中详细解答。

试题分析及解答

****************************************************

问题 A(1146): 【基础算法】删数游戏(版本1)

时间限制: 1 Sec
内存限制: 64 MB

题目描述
输入一个高精度的正整数S,去掉其中任意N个数字后剩下的数字按原左右次序将组成一个新的整数。 编程对给定的S和N,寻找一种方案使得剩下的数字组成的新数最小。 输入数据均不需判错。输出新的整数(S不超过255位)。

输入
第1行:1个数字字符串S和1个整数N,两者用1个空格分开。

输出
第1行:删除后剩下的数字串。

样例输入

178543

4

样例输出

13

提示

【A题分析】
由于正整数n的有效位数最大可达255位,所以可以采用字符串类型来存储n。那么,应如何来确定该删除哪s位呢?是不是只要删掉最大的s个数字就可以了呢?
为了尽可能地逼近目标,我们选取的贪心策略为:每一步总是选择一个使剩下的数最小的数字删去,即按高位到低位的顺序搜索,若各位数字递增,则删除最后一个数字,否则删除第一个递减区间的首字符。然后回到串首,按上述规则再删除下一个数字。重复以上过程s次,剩下的数字串便是问题的解了。

【注意事项】
①在for循环里需要加break,否则可能无法退出。

if((a[i+1]<a[i]&&i+1<a.length())||(a[i]==a.length()-1))
    {
        a.erase(i,1);
        s--;
        **break;**
    }  

②数组需在开始时就定大,否则可能会遗忘。

【题解】

#include<cstdio>
#include<iostream>
using namespace std;
string a;
int s;
int main()
{
    cin>>a;
    scanf("%d",&s);
    while(s>0)
    {
    for(int i=0;i<a.length();i++)
    {
    if(a[i+1]<a[i])
    {
        a.erase(i,1);
        s--;
        break;
    }   
    }
    }
    while(a[0]=='0'&&a[1])
    {a.erase(0,1);}
    for(int i=0;i<a.length();i++)
        printf("%c",a[i]);
}
****************************************************

问题 B(1147): 取数游戏

时间限制: 1 Sec
内存限制: 64 MB

题目描述
给出2×n个自然数。游戏双方分别为A方(计算机)和B方(对弈的人)。只允许从数列两头取数。A先取,然后双方依次轮流取数。取完时,取得的数字总和最大者为胜方;双方和相等,属于A胜。试问A方可否有必胜的策略?
输入
第1行:1个整数n(1<=n<=100) 第2行:2×n个整数(每个数<30000),相邻两个数之间用空格分开。
输出
第1行:2个整数,分别为A方取得的数之和与B方取得的数之和。
样例输入

2
1 2 3 4

样例输出

6 4

提示

【B题分析】
其实,面对这道题,我们不需要费太大的脑经,不需要想太复杂。就算是最基础的人也能做。看懂题目的人就知道:从一开始的坐标是奇数,而从最后往前面开始的,坐标一定是偶数。(因为数的个数是2*n)。所以,这道题就简单啦。只需枚举两个奇偶队列,然后再加起来,看谁最大,就先输出谁。

【注意事项】

【题解】

#include<algorithm>
#include<cstdio>
using namespace std;
int a[1002];
int maxa,max2;
int n;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n*2;i++)
    {scanf("%d",&a[i]);}
    for(int i=1;i<=n*2;i+=2)
        maxa+=a[i];
    for(int i=2;i<=n*2;i+=2)
        max2+=a[i];
    if(maxa>max2) printf("%d %d",maxa,max2);
        else
        {
            printf("%d %d",max2,maxa);
            }
}
****************************************************

问题 C(1148): 活动选择

时间限制: 1 Sec
内存限制: 64 MB

题目描述
假设有一个需要使用某一资源的活动组成的集合S,S={1,……n}.(n<1000)该资源一次只能被一个活动占用,每一个活动有一个开始时间bi和一个结束事件ei(bi <=ei).若bi >=ej或者bj >=ei,则活动i和活动j兼容。 你的任务是:选择由相互兼容的活动组成的最大集合。

输入
输入共n+1行,其中第1行为n,第2行到第n+1行表示n个活动的开始时间和结束事件。(中间用空格隔开。)格式为 n b1 e1 …… bn en

输出
输出共两行,第一行为满足要求的活动所占用的总时间t。(可以理解为选择的活动中最后一个活动的结束时间) 第二行为最大集合中的活动序号。每个数据见用一个空格隔开。

样例输入

11
3 5
1 4
12 14
8 12
0 6
8 11
6 10
5 7
3 8
5 9
2 13

样例输出

14
2 3 6 8

提示

【C题分析】
总集合中放入一个活动,那么这个活动的完成时间要尽可能的早,这样才能保证剩余的活动时间更多,所以就需要将结束的时间从小到大的排序。然后就看第i个的结束时间是否小于等于第i+1个活动时间的起始时间,是的话,算上,反之。然后,在此题中,还有一个需要注意的是:样例中最后输出的是活动的行号,即第几行的活动。但在我们排序后,活动的行号早已改变,所以,在开始输入时,我们就要做好准备,记录开始时的行号。而在后面的判断中(即上述的判断),一旦它加入,我们就要用另外的一个数组来储存它的初始行号。以便后面排序输出。

【注意事项】

【题解】

#include<bits/stdc++.h>
using namespace std;
struct node
{
    int a,b;
    int n;
}tree[1005];
int cmp(node a,node b)
{
return a.b<b.b;  
}
int n;
int b[1005];
int m;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {scanf("%d%d",&tree[i].a,&tree[i].b);tree[i].n=i;}
    sort(tree+1,tree+1+n,cmp);
    int j=0;
    int num=0;
    for(int i=1;i<=n;i++)
    {     
        if(tree[i].a>=j)
        {
            b[++m]=tree[i].n;
            j=tree[i].b;
        }
    }
    printf("%d\n",j);
    sort(b+1,b+m+1);
    for(int i=1;i<=m;i++)
        printf("%d ",b[i]);
}
****************************************************

问题 D(1149): 【基础算法】雇佣计划

时间限制: 1 Sec
内存限制: 64 MB

题目描述
一位管理项目的经理想要确定每个月需要的工人,他当然知道每月所需的最少工人数.当他雇佣或解雇一个工人,会有一些额外支出.一旦一个工人被雇佣,即使他不工作,他也将得到工资.这位经理知道雇佣一个工人的费用,解雇一个工人的费用和一个工人的工资.现他在考虑一个问题:为了把项目的费用工致在最低,他将每月雇佣或解雇多少个工人?
输入
第1行:1个整数n(n<=12),表示月数。

第2行:3个用空格分开的整数h, s, f,分别表示雇佣一个工人的费用;一个工人的月工资和解雇一个工人的费用。

第3行:n个用空格分开的整数,分别表示每个月最少需要的工人数(每个月的工人数<=1000)

输出
第1行:1个整数,表示项目的最小总费用

样例输入

3
4 5 6
10 9 11

样例输出

199

提示

此题暂不评讲

****************************************************

问题 E(1469): 贪心算法训练题三

时间限制: 1 Sec
内存限制: 64 MB

题目描述
有 N 堆纸牌,编号分别为 1,2,…, N。每堆上有若干张,但纸牌总数必为 N 的倍数。可以在任一堆上取若于张纸牌,然后移动。   移牌规则为:在编号为 1 堆上取的纸牌,只能移到编号为 2 的堆上;在编号为 N 的堆上取的纸牌,只能移到编号为 N-1 的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。   现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。   例如 N=4,4 堆纸牌数分别为:   ① 9 ② 8 ③ 17 ④ 6   移动3次可达到目的:   从 ③ 取 4 张牌放到 ④ (9 8 13 10) -> 从 ③ 取 3 张牌放到 ②(9 11 10 10)-> 从 ② 取 1 张牌放到①(10 10 10 10)。

输入
N(N 堆纸牌,1 <= N <= 100) A1 A2 … An (N 堆纸牌,每堆纸牌初始数,l<= Ai <=10000)

输出
所有堆均达到相等时的最少移动次数。

样例输入

4
9 8 17 6

样例输出

3

提示

【E题分析】
我们要使移动次数最少,就是要把浪费降至零。通过对具体情况的分析,可以看出在某相邻的两堆之间移动两次或两次以上,是一种浪费,因为我们可以把它们合并为一次或零次。如果你想到把每堆牌的张数减去平均张数,题目就变成移动正数,加到负数中,使大家都变成0,那就意味着成功了一半!
从第i堆移动-m张牌到第i+1堆,等价于从第i+1堆移动m张牌到第i堆,步数是一样的。
注意:最左边和左右边的0不算移动次数
【注意事项】

【题解】

#include<algorithm>
#include<cstdio>
using namespace std;
int n;
int a[105];
int ans;
int num;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {scanf("%d",&a[i]);ans+=a[i];}
    ans=ans/n;
    for(int i=1;i<=n;i++)
        a[i]-=ans;
    for(int i=1;i<=n;i++)
        if(a[i])
        {
            a[i+1]=a[i]+a[i+1];
            a[i]=0;
            num++;
        }
    printf("%d",num);
}
****************************************************

问题 F(1471): 贪心算法专题训练五

时间限制: 1 Sec
内存限制: 64 MB

题目描述
期待这一份幸运,和一份冲劲,多么奇妙的际遇……。燕姿在演唱完绿光这首歌后,出给了姿迷一个考题。 北欧有一个传说! 人一生中能看见绿光! 他就一生都可以得到幸福! 燕姿唱完这首歌,天上降落了一道绿光,在地上形成了一个矩形的映射,矩形的长为a,宽为b。燕姿向姿迷出了一个考题,谁能够把这个矩形绿光阵分成若干个正整数的正方形,谁的正方形边长之和最小,他就将得到燕姿的一个合影。姿迷们都很想得到合影,可是怎么分才最小呢?大家都束手无策,现在,这个问题交给你了。 歌迷X:呜呜呜,俺的语文不好,听不懂你在讲什么。 燕姿:别怕,其实这个问题可以简化为…… 将边长为正整数a,b的长方形划分成若干边长均为正整数,每个正方形的边均平行于矩形的相应边,试求这些正方形边之和的最小值MIN。 (如果这个长方形可以分成N个正方形,其中每个边长为Ai,那么MIN=A1+A2+^^^+AN 注意,数组A中的元素可能相等)
输入
一共10行 每行两个正整数,Ai,Bi 对于30%的数据: Ai,Bi小于MAXINT 对于100%的数据: Ai,Bi小于MAXLONGINT;
输出
一共10行 每行一个整数,输出MINi
样例输入

1 1
2 1
3 1
4 1
5 1
6 1
7 1
8 1
9 1
10 1

样例输出

1
2
3
4
5
6
7
8
9
10

提示

【F题分析】

此题分析已入代码中

【注意事项】

每个数据中的ans,记得归零。

【题解】

#include<algorithm>
#include<cstdio>
using namespace std;
int main()
{
    for(int i=1;i<=10;i++)
    {

        long long a,b;
        long long ans;
        long long maxl,minl;
    ans=0;
    scanf("%lld%lld",&a,&b);//共有十组数据
    while(a&&b)//如果a,b(长方形的长和宽)都不为零,则计算
    {
    maxl=max(a,b);//找出最长边
    minl=min(a,b);//找出最短边
    ans+=maxl/minl*minl;//加上最大正方形
    if(a==maxl)//如果a是最大边
    {
    a%=minl;//就看它等于几条最小边,即几个正方形
    }
    else//反之
    {
    b%=minl;    
    }
    }
    printf("%lld\n",ans);//输出
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值