HNUCM2020年春季ACM集训队选拔赛(2)题解

问题 A: 爱的日期

题目描述

Inter和AMD刚刚在上个学期确定了恋爱关系,但是由于要期末考试,他们没法have a appointment。
所以他们打算在2月14日情人节那天一起出去。恰恰最近疫情爆发,Inter和AMD都被关在机箱里面不准出来。
于是Inter就想找一个普通而又特殊的日子再次和AMD约会。 要是个周五,然后这个周五是当月的20号岂不美哉?
你能帮帮Inter找出当年中既是周5,又是20号的月份吗。(找不到Inter就只能伤心的回去挤牙膏了)

输入

输入包含多组数据,每组数据包含一个正整数year(2000≤year≤9999)。

输出

对应每一组数据,输出所有符合条件的月份,月份之间用空格隔开。
如果当年不存在20号是星期五的月份,就输出一行jiyagao。

样例输入

2000
2001
2002

样例输出

10
4 7
9 12

思路

因为year的范围是[2000,9999]我们可以计算出给定年份每个月13号距离2000年1月1日的天数,用2000年1月1日的星期推出每个月13号的星期,至于2000年1月1日这一天的星期…好吧,题目没有告诉
其实只需要简(繁)单(琐)地计算一下…(其实偷偷看下电脑日历就行(●′ω`●))
2000.1.1是星期六那2000.1.7就是星期五,那就是看这一年每个月13号和2000.1.7的日期之差是不是7的倍数

#include<bits/stdc++.h>
using namespace std;
int a[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
int b[13]={0,31,29,31,30,31,30,31,31,30,31,30,31};
int ju(int year){
    if((year%4==0&&year%100!=0)||year%400==0){
        return 1;
    }
    return 0;
}
int main()
{
    int year,month,day,s[13];
    while(~scanf("%d",&year)){
        int num=6,ls=0;
        for(int i=2000;i<year;++i){
            num+=365;
            if(ju(i))
                ++num;
        }
        if(ju(year)){//闰年
            for(int i=1;i<12;++i){
                if(num%7==0)
                    s[++ls]=i;
                num+=b[i];
            }
            if(num%7==0)
                s[++ls]=12;
        }else{//平年
            for(int i=1;i<12;++i){
                if(num%7==0)
                    s[++ls]=i;
                num+=a[i];
            }
            if(num%7==0)
                s[++ls]=12;
        }
        if(ls){
            for(int i=1;i<ls;++i)
                printf("%d ",s[i]);
            printf("%d\n",s[ls]);
        }else
            printf("jiyagao\n");
    }
    return 0;
}

问题 B: 乒乓球筐

题目描述

Kimi有两盒(A、B)乒乓球,有红双喜的、有亚力亚的……现在他需要判别A盒是否包含了B盒中所有的种类,并且每种球的数量不少于B盒中的数量,该怎么办呢?

输入

输入有多组数据。
每组数据包含两个字符串A、B,代表A盒与B盒中的乒乓球,每个乒乓球用一个大写字母表示,即相同类型的乒乓球为相同的大写字母。
字符串长度不大于1000。

输出

每一组输入对应一行输出:如果B盒中所有球的类型在A中都有,并且每种球的数量都不大于A,则输出“Yes”;否则输出“No”。

样例输入

ABCDFYE CDE
ABCDGEAS CDECDE

样例输出

Yes
No

思路

字符的ASCII码不大,可以用数组保存某个字符出现的次数
然后遍历两遍字符串得到答案

#include<bits/stdc++.h>
using namespace std;
char a[1005],b[1005];
int vis_a[205],vis_b[205];
int main()
{
    while(~scanf("%s %s",a+1,b+1)){
        int la=strlen(a+1),lb=strlen(b+1),ans=1;
        memset(vis_a,0,sizeof(vis_a));//将vis_a置0
        memset(vis_b,0,sizeof(vis_b));
        for(int i=1;i<=la;++i){
            ++vis_a[a[i]];
        }
        for(int i=1;i<=lb;++i){
            ++vis_b[b[i]];
            if(vis_b[b[i]]>vis_a[b[i]]){//某一字符B串出现的次数大于A串
                ans=0;
                break;
            }
        }
        if(ans){
            printf("Yes\n");
        }else{
            printf("No\n");
        }
    }
    return 0;
}

问题 C: 最难的问题

题目描述

Kimi生活在充满危险和阴谋的年代。为了生存,他首次发明了密码,用于军队的消息传递。假设你是军团中的一名军官,需要把发送来的消息破译出来、并提供给你的将军。
消息加密的办法是:对消息原文中的每个字母,分别用该字母之后的第5个字母替换(例如:消息原文中的每个字母A 都分别替换成字母F),其他字符不变,并且消息原文的所有字母都是大写的。密码中的字母与原文中的字母对应关系如下。
密码字母:A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
原文字母:V W X Y Z A B C D E F G H I J K L M N O P Q R S T U

输入

输入包括多组数据,每组数据一行,为收到的密文。
密文仅有空格和大写字母组成。(长度<=1000)

输出

对应每一组数据,输出解密后的明文。

样例输入

HELLO WORLD
SNHJ

样例输出

CZGGJ RJMGY
NICE

思路

用一个数组保存对应关系,直接输出

#include<bits/stdc++.h>
using namespace std;
char f[26]={'V','W','X','Y','Z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U'};
char s[1005];
int main()
{
    while(gets(s)){
        int ls=strlen(s);
        for(int i=0;i<ls;++i){
            if(s[i]>='A'&&s[i]<='Z'){
                printf("%c",f[s[i]-'A']);
            }else{
                printf("%c",s[i]);
            }
        }
        putchar('\n');
    }
    return 0;
}

或者稍微计算一下(mod=26)
(原码+5)%mod -> 密码

(密码-5+mod)%mod -> 原码
也就是
(密码+21)%mod -> 原码

((x-'A')+21)%26+'A'=y
#include<bits/stdc++.h>
using namespace std;
char s[1005];
int main()
{
    while(gets(s)){
        int ls=strlen(s);
        for(int i=0;i<ls;++i){
            if(s[i]>='A'&&s[i]<='Z'){
                printf("%c",(s[i]-'A'+21)%26+'A');
            }else{
                printf("%c",s[i]);
            }
        }
        putchar('\n');
    }
    return 0;
}

问题 D: 快乐的木头

题目描述

现有一堆快乐的木头,它们排成一行,对于它们而言,快乐是无价的,因此它们各自都有一个快乐值a,而快乐值低的木头总想与比它更快乐的木头玩成一团,
而对于快乐值高的木头也总是乐意与在它邻近的快乐值比它低的左边的木头玩成一团。
这也就是说,对于某个区间[l,r]内的木头,它们的快乐值如果是单调递增的,那么它们就会作为一个小团体,一起快乐的玩耍,注意这个小团体一定是不能再
继续左右扩大的,也就是说,对于l和r满足a(l+1) >= a(l) && a (r ) >= a(r+1)。现在河神想问你,这些木头将会分成多少个团体。
(至于你问为啥一块木头不会和左边比它一样或者更快乐的木头玩成一团emmm,这是个值得深思的问题。)

输入

第一行输入一个T(T<=5),表示测试数据总数。
每组测试数据仅一行,每行首先输入一个n(n <= 1000000),接下来输入n个正整数ai。(ai <= 1000000)

输出

输出分成的快乐团体个数。

样例输入

1
5
2 7 2 3 3

样例输出

3

提示

样例中分为三个团体:(2,7),(2,3),(3)

思路

题目大意是说求单调上升的区间的数量(而且这个区间是不能向左或者向右扩大的)
图像大致是这样的
在这里插入图片描述也就是说
每一个区间内部都是上升的
而每两个区间之间是不上升的
所以答案就是不上升的次数+1

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e6+5;
const int inf=0x3f3f3f3f;
int main()
{
    int t,n,x,now,ans;
    scanf("%d",&t);
    while(t--){
        now=-inf,ans=1;//now代表上一个位置的数字
        scanf("%d",&n);
        for(int i=1;i<=n;++i){
            scanf("%d",&x);
            if(x<=now){
                ++ans;
            }
            now=x;
        }
        printf("%d\n",ans);
    }
    return 0;
}

问题 E: 努力的木头

题目描述

现有一堆努力的木头,它们非常的努力学习,于是问题来了,它们中有的擅长空间几何(有a块木头),有的擅长高等代数(有b块木头),
有的擅长数学分析(有c块木头),现在你有d道空间几何题,e道高等代数题,f道数学分析题,现在你想让这些木头帮助你做这些题目,
但是它们做空间几何需要g分钟,做高等代数需要h分钟,做数学分析需要i分钟,你当然想直接把题目交给他们,然后开始玩游戏,
但是在它们完成之前,你都必须监督它们,否则它们将消极怠工,请问你需要监督它们做多久。注意每块木头只能完成自己擅长的题目,
并且在做某一题的时间内不能停下来去做别的。

输入

第一行输入一个T(T<=10),表示有测试数据的数目。
每组测试数据仅一行,一行包括9个正整数a,b,c,d,e,f,g,h,i(a,b,c,d,e,f,g,h,i<=100)

输出

输出你所需要监督它们的最少时间。

样例输入

1
2 3 4 5 6 7 8 9 10

样例输出

24

思路

因为可以同时对所有人进行监督,所以监督的最少时间就是,做完三类题面所用时间的最大值
人数a b c
题目e f g
时间h i j
以第一类题目为例,需要 (a/e向上取整)*h那么多时间
向上取整可以((a+e-1)/e)或者(a/e+(a%e!=0))

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int t,a,b,c,d,e,f,g,h,i;
    scanf("%d",&t);
    while(t--){
        scanf("%d %d %d %d %d %d %d %d %d",&a,&b,&c,&d,&e,&f,&g,&h,&i);
        int ans1=((d+a-1)/a)*g;
        int ans2=((e+b-1)/b)*h;
        int ans3=((f+c-1)/c)*i;
        printf("%d\n",max(ans1,max(ans2,ans3)));
    }
    return 0;
}

问题 F: 神奇的木头

题目描述

现有一堆神奇的木头,他们之所以是神奇的,是因为他们有自己的智商值,而他们能够通过两种途径改变你的智商,第一种就是让你的智商值加上它的智商
值,第二种就是让你的智商值乘以它的智商值。但是河神告诉你,你必须从左往右使用这些木头,否则,它们不但不会改变你的智商,反而会让你成为传说
中智商为0的RZ,当然咯,那你的目的当然是让你自己的智商最高最好。

输入

第一行输入一个T(T<=5),表示测试数据组数。
每组测试数据有2行,第一行首先输入一个正整数n,表示有n块神奇的木头,接下来从左往右输入木头的智商值,注意:0<=木头的智商值<=10,n<=10。
第二行输入你的初始智商值k:0<=k<=10。

输出

输出你经过改变后的智商最高值。(答案不会出现为0的情况哈)

样例输入

1
4 1 2 3 4
0

样例输出

36

提示

对于样例我们显然可以(0+1+2)34 = 36

思路

直接进行判断哪种选择加的智商值最多
ans=max(ans+x,ans*x);

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int t,n,ans,a[15];
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(int i=1;i<=n;++i){
            scanf("%d",&a[i]);
        }
        scanf("%d",&ans);
        for(int i=1;i<=n;++i){
            ans=max(ans+a[i],ans*a[i]);
        }
        printf("%d\n",ans);
    }
    return 0;
}

问题 G: 无聊的木头

题目描述

现有一堆无聊的木头,它们的长度已知且排成一行,突然有一天,第一块木头突发奇想,它想要知道在它的右边比它长度次小的木头是哪一块,于是
它把这个想法告诉了其他木头,而其他木头对于它这个想法也十分好奇,因此你需要告诉它们在它们右边的哪一块木头的长度比它次小,也就是找到
比它次小的那块木头的位置。

输入

第一行输入一个T(T <= 20),表示测试数据组数。
接下来T行,每行首先输入一个n(n <= 100000),表示木头的个数,之后输入n个正整数,分别表示木头的长度(木头长度<=1000000)

输出

输出n个满足条件的答案,注意如果没有满足条件的答案你只需要输出0就好了,如果对于某块木头有多个满足条件的答案,你需要输出答案尽量小的那一个。

样例输入

1
6 10 9 26 10 10 18

样例输出

2 0 6 0 0 0

思路

(x的次小就是仅次于x的那个值)
大意是求出每个位置的木头右边比它小的最大木头的位置
如果n的范围不大,可以直接直接暴力求解但是这里的n的上限是100000两层循环会超时

#include<bits/stdc++.h>
using namespace std;
int a[1005],b[1005];
int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--){
        memset(b,0,sizeof(b));
        scanf("%d",&n);
        for(int i=1;i<=n;++i){
            scanf("%d",&a[i]);
        }
        for(int i=1;i<n;++i){
            int x=0,now=-1;//now保存比第i个木头小的最大木头高度
            for(int j=i+1;j<=n;++j){//循环遍历后续所有木头
                if(a[j]<a[i]&&a[j]>now){
                    x=j,now=a[j];
                }
            }
            if(x){
                b[i]=x;
            }
        }
        for(int i=1;i<n;++i){
            printf("%d ",b[i]);
        }
        printf("%d\n",b[n]);
    }
    return 0;
}

另一种思路是将每一个木头的高度和对应的位置用结构体保存起来,按高度升序排序
循环遍历每个木头,对每一个木头又用一层循环找到第一个原始坐标小于该木头的木头
(可能有点绕๑乛◡乛๑)
总的来说,一般情况下,这会降低时间复杂度,但是如果碰到极端数据,比如全部高度都一样或者木头初始是升序排序的话其实和暴力求解是一样的所以也会被卡
如果不知道sort或者bool operator < (const node &x)const部分可以点下方链接稍微了解一下
点我(●’◡’●)ノ

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100005;
struct node{
    int id;
    int h;
    bool operator < (const node &x)const{
        if(h == x.h) return id > x.id;
        return h < x.h;
    }
}a[maxn];
int ans[maxn];
int main(){
    int t;
    scanf("%d", &t);
    while(t--){
        memset(ans, 0, sizeof(ans));
        int n;
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i){
            scanf("%d", &a[i].h);
            a[i].id = i;
        }
        sort(a + 1, a + n + 1);
        for(int i = 1; i <= n; ++i){
            for(int j = i - 1; j >= 1; --j){
                if(a[j].h < a[i].h && a[j].id > a[i].id){
                    ans[a[i].id] = a[j].id;
                    break;
                }
            }
        }
        for(int i=1;i<n;++i){
            printf("%d ",ans[i]);
        }
        printf("%d\n",ans[n]);
    }
    return 0;
}
set写法

从右至左遍历数组,将遍历过的木头高度存入set中
对于第i个木头用lower_bound函数找到大于等于该木头高度的木头高度h,它的前一个就是次小的木头高度,h已经是第一个,也就意味着没有木头比第i个木头小
我们知道了次小的木头高度,因为木头高度上限为1000000,可以开数组id[i]表示高度为i的木头的位置
C++set点我(●’◡’●)ノ

AC代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int a[maxn],b[maxn],id[maxn*10];
set<int> w;
set<int>::iterator it;
int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--){
        w.clear();
        memset(id,0,sizeof(id));
        scanf("%d",&n);
        for(int i=1;i<=n;++i){
            scanf("%d",&a[i]);
        }
        for(int i=n;i>=1;--i){
            w.insert(a[i]);
            it=w.lower_bound(a[i]);//此处不能用it=lower_bound(w.begin(),w.end(),a[i])...原因不明
            if(it==w.begin()){
                b[i]=0;
            }else{
                b[i]=id[*(--it)];
            }
            id[a[i]]=i;//最左边高度为a[i]的木头的位置
        }
        for(int i=1;i<n;++i){
            printf("%d ",b[i]);
        }
        printf("%d\n",b[n]);
    }
    return 0;
}
线段树写法

和set有些相似,同样是反向遍历
不过找高为h的木头右边小于h的最大木头的坐标用线段树维护
线段树每一个叶子节点i保存的是高为i的木头的原始位置
而找该木头的次小就变成了找1到h-1这个区间最右端的那个非0值
从右至左遍历木头,每找一个木头右边的次小就将这个木头坐标直接插入线段树中
线段树从0开始
讲线段树的博客很多,下面是较详细的两篇
添加链接描述
添加链接描述

AC代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
int a[100005],b[100005];
struct node{
    int l,r,id;
};
node tr[maxn<<2];
void build(int e,int l,int r){
    tr[e].l=l,tr[e].r=r,tr[e].id=0;
    if(l==r)
        return;
    int mid=(l+r)>>1;
    build(e<<1,l,mid);
    build(e<<1|1,mid+1,r);
}
void add(int e,int x,int y){
    if(tr[e].l==tr[e].r){
        tr[e].id=y;
        return;
    }
    int mid=(tr[e].l+tr[e].r)>>1;
    if(x<=mid)
        add(e<<1,x,y);
    else
        add(e<<1|1,x,y);
}
int query(int e,int x){//查询1-x这个区间最高的木头的位置
    if(tr[e].l==tr[e].r){
        if(tr[e].l<x)
            return tr[e].id;
        return 0;
    }
    int mid=(tr[e].l+tr[e].r)>>1,ju=0;
    if(mid+1<x)//如果x在右子树->找右子树
        ju=query(e<<1|1,x);
    if(ju)//右子树找到了就返回不必再找左子树了
        return ju;
    if(tr[e].l<x)
        return query(e<<1,x);
    return 0;
}
int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--){
        build(1,1,maxn);
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
            scanf("%d",&a[i]);
        for(int i=n;i>=1;--i){
            b[i]=query(1,a[i]);
            add(1,a[i],i);
        }
        for(int i=1;i<n;++i)
            printf("%d ",b[i]);
        printf("%d\n",b[n]);
    }
    return 0;
}

问题 H: 值钱的木头

题目描述

现有一堆值钱的木头,它们排成一行,河神给了你一个可以改变一个区间内的木头的价值的机会(注意这个区间不能越过边界),因为这是河神给你的选择,因此你必须要把握这次机会,也就是说你必须改变一个区间内木头的价值。
当然你的目的是让这堆木头的总价值最高。

输入

第一行输入一个T(T <= 5),表示测试数据总数。
接下来2*T行,每组数据有2行。
每组数据第一行首先输入一个n(n <= 100000),接下来输入n个整数,分别表示这些木头的价值(-1000<=木头价值<=1000)
每组数据第二行输入两个整数x,y,其中x表示可以改变的区间的大小,y表示你可以把这个区间内的木头价值变为y(1<= x <= n,-1000<=y<=1000)

输出

你需要输出你所能获得的最大价值(这个价值可能为负数,表明你可能被河神给坑了)

样例输入

1
5
2 -5 -5 23 -11
1 3

样例输出

18

提示

显然你只需要把-11改为3就可以得到最大价值18。

思路

题目说了必须选一个区间!!!不注意也会被坑到
所以答案就是1到k ,2到k+1,3到k+2,…,n-k+1到n中选一个区间替换后的最大值
可以两层循环计算每一种情况,这会运行k*(n-k)次
k<n<100000,最差情况k=50000 n=100000 要运行2500000000次会超时

对于替换的区间我们知道替换后的价值x*y
所以我们只要知道替换前的价值便可以知道替换后这堆木头的总价值
设all是木头初始价值和,a是替换前区间价值,b是替换后区间价值,替换后木头总价值和为all-a+b
所以重点就在如何快速计算每个区间的初始价值和

可以初始化1到k区间的值之和,向右遍历,不断更新区间值之和,便获得每一个区间的替换前价值,便可以算出替换后木头总价值和,取最大值

#include<bits/stdc++.h>
using namespace std;
int s[100005];
int main()
{
    int t,n,x,y,a,b,ans,all;
    scanf("%d",&t);
    while(t--){
        all=0;
        scanf("%d",&n);
        for(int i=1;i<=n;++i){
            scanf("%d",&s[i]);
            all+=s[i];
        }
        scanf("%d %d",&x,&y);
        a=0,b=x*y;//a表示这个区间的值之和,b表示将这个区间所有值替换成y的值之和
        for(int i=1;i<=x;++i){
            a+=s[i];
        }
        ans=all-a+b;//1-k
        for(int i=x+1;i<=n;++i){
            a+=s[i];//随区间的前进更新a的值
            a-=s[i-x];
            ans=max(ans,all-a+b);//保持原样,和替换,取最大值
        }
        printf("%d\n",ans);
    }
    return 0;
}

也可以用前缀
关于前缀下方链接有简单的介绍
点我(●’◡’●)ノ

#include<bits/stdc++.h>
using namespace std;
int s[100005],pre[100005];
int main()
{
    int t,n,x,y,b,ans,all;
    scanf("%d",&t);
    while(t--){
        all=0;
        memset(pre,0,sizeof(pre));
        scanf("%d",&n);
        for(int i=1;i<=n;++i){
            scanf("%d",&s[i]);
            all+=s[i];
        }
        scanf("%d %d",&x,&y);
        for(int i=1;i<=n;++i){//求前缀
            pre[i]=pre[i-1]+s[i];
        }
        b=x*y;//b表示将这个区间所有值替换成y的值之和
        ans=all+b-pre[x];
        for(int i=x;i<=n;++i){
            ans=max(ans,all-pre[i]+pre[i-x]+b);
        }
        printf("%d\n",ans);
    }
    return 0;
}
发布了147 篇原创文章 · 获赞 35 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43984169/article/details/104574510