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

问题 A: 互异字符串
题目描述

请实现一个算法,确定一个字符串的所有字符是否全都不同。
给定一个字符串,请返回一个True代表所有字符全都不同,False代表存在相同的字符。

输入

输入一个字符串。保证字符串中的字符为ASCII字符。字符串的长度小于等于3000。

输出

如果所有字符全都不同输出“True”,如果存在相同的字符则输出“False”。

样例输入

aeiou
BarackObama

样例输出

True
False

思路:

因为字符数量不多,可以用数组进行标记,遍历一遍字符串,如果出现相同的字符就break

#include<bits/stdc++.h>
using namespace std;
char s[3005];
int main()
{
    int mp[305];
    while(~scanf("%s",s+1)){
        memset(mp,0,sizeof(mp));
        int ls=strlen(s+1),ju=1;
        for(int i=1;i<=ls;++i){
            if(mp[s[i]]==0){
                mp[s[i]]=1;
            }else{
                ju=0;
                break;
            }
        }
        if(ju){
            printf("True\n");
        }else{
            printf("False\n");
        }
    }
}
问题 B: 第k个数
题目描述

有一些数的素因子只有3、5、7,请设计一个算法,找出其中的第k个数。

输入

给定一个正整数k,保证k小于等于100。

输出

输出第k个数。

样例输入

1
3
4
5

样例输出

3
7
9
15

思路:

最容易想到的就是暴力的写法,遍历3到一个很大的数字,判断是是否只由3,5,7相乘得到

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
int ans[maxn],la;
int main()
{
    int k,m;
    for(int i=3;i<maxn;++i){
        m=i;
        while(m%3==0){
            m/=3;
        }
        while(m%5==0){
            m/=5;
        }
        while(m%7==0){
            m/=7;
        }
        if(m==1){
            ans[++la]=i;
        }
    }
    while(~scanf("%d",&k)){
        printf("%d\n",ans[k]);
    }
}

也可以这样看,每个数都可以分别乘以3,5,和7(当然会有重复)也就是有三个分支
令根节点为1…就得到一颗三叉树(由于画得太丑就不放出来了),这个树中每个节点的数都符合要求,现在的问题就是如何能知道哪个数是第k大的
可以利用优先队列,将初始的3,5,7放到优先队列中(我们自定义一下,小的在上),每次取出最小的那个数(定为x),再将x乘3,乘5,乘7分别加入优先队列中…循环
显然我们第一次取出的数便是第一小的,第二次便是第二小的…
第k次取出的数便是答案

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
int main()
{
    int k;
    while(~scanf("%d",&k)){
        priority_queue<ll,vector<ll>,greater<ll> >pq;
        pq.push(3),pq.push(5),pq.push(7);
        ll x;
        while(k--){
            x=pq.top();
            while(pq.top()==x){//可能有重复的数
                pq.pop();
            }
            pq.push(x*3);
            pq.push(x*5);
            pq.push(x*7);
        }
        printf("%lld\n",x);
    }
}

当然也可以对上面的思路进行简化(由于确实不太好解释简化的思路就偷一下懒…哈哈)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
int ans[maxn];
int main()
{
    int n;
    while(~scanf("%d",&n)){
        memset(ans,0,sizeof(ans));
        ans[0]=1;
        int a=0,b=0,c=0;
        for(int i=1;i<=n;++i){
            int x=min(ans[a]*3,min(ans[b]*5,ans[c]*7));
            ans[i]=x;
            if(x==ans[a]*3){
                ++a;
            }
            if(x==ans[b]*5){
                ++b;
            }
            if(x==ans[c]*7){
                ++c;
            }
        }
        printf("%d\n",ans[n]);
    }
}
问题 C: 2的个数
题目描述

请编写一个程序,输出0到n(包括n)中数字2出现了几次。

输入

输入一个正整数n。

输出

输出0到n的数字中2出现了几次。

样例输入

2
10
20

样例输出

1
1
3

思路

直接枚举2到n,循环计算2的数量(暴力大法好)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
int main()
{
    ll n,m,ans;
    while(~scanf("%lld",&n)){
        ans=0;
        for(ll i=2;i<=n;++i){
            m=i;
            while(m){
                if(m%10==2){
                    ++ans;
                }
                m/=10;
            }
        }
        printf("%lld\n",ans);
    }
}
问题 D: 外星人的语言
题目描述

Kimi费了很大劲,终于和地外文明联系上。
我们地球人通常有10根手指,因此我们习惯用10进制的数,而外星人的手指有16跟、8根等不等的数目,因此他们使用与我们不同的进制。
为了方便沟通,需要你开发一款工具,把地球人的10进制转换成外星人的R进制形式。

输入

输入有多行。
每行包括两个正整数n和R,其中2≤R≤16。
输入直到文件结束为止。

输出

对于每个用例,输出n对应的R进制形式。
超过10进制的数,10用A表示、11用B表示,依次类推。

样例输入

1989 2
1119 16

样例输出

11111000101
45F

思路

算是简单的进制转化

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
int ans[maxn],la;
int main()
{
    ll n,r,m;
    while(~scanf("%lld %lld",&n,&r)){
        la=0,m=n;
        while(m){
            ans[++la]=m%r;
            m/=r;
        }
        for(int i=la;i>=1;--i){
            if(ans[i]>9){
                printf("%c",'A'+ans[i]-10);
            }else{
                printf("%d",ans[i]);
            }
        }
        putchar('\n');
    }
}
问题 E: 3n+1猜想
题目描述

卡拉兹(Callatz)猜想:

对任何一个自然数n,如果它是偶数,那么把它砍掉一半;如果它是奇数,那么把(3n+1)砍掉一半。这样一直反复砍下去,最后一定在某一步
得到n=1。卡拉兹在1950年的世界数学家大会上公布了这个猜想,传说当时耶鲁大学师生齐动员,拼命想证明这个貌似很傻很天真的命题,
结果闹得学生们无心学业,一心只证(3n+1),以至于有人说这是一个阴谋,卡拉兹是在蓄意延缓美国数学界教学与科研的进展……

我们今天的题目不是证明卡拉兹猜想,而是对给定的任一不超过1000的正整数n,简单地数一下,需要多少步(砍几下)才能得到n=1?

输入

每个测试输入包含1个测试用例,即给出自然数n的值。

输出

输出从n计算到1需要的步数。

样例输入

3

样例输出

5

思路

按题目讲的来就行

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
int ans[maxn],la;
int main()
{
    ll n;
    while(~scanf("%lld",&n)){
        int ans=0;
        while(n>1){
            if(n&1){
                n=(3*n+1)/2;
            }else{
                n=n/2;
            }
            ++ans;
        }
        printf("%d\n",ans);
    }
}
问题 F: 狗狗大决杀
题目描述

这是 10 月 32 日发生的事情。抽筋流高手布置了一个非常 BT 的作业。他说要在一定时间内把屏幕上的所有小狗布成一条直线。而这个时间非常之短!!!众人大呼 BT,但是抽筋流高手是非常严格的,所以大家只好遵从他的命令。

众所周知,只有达达的 APM 在 190 以上,所以最后完成作业的只有他一个。他受到了 抽筋流高手的表扬。

然而罗小松觉得不爽,于是他趁达达去 WC 的时候在那一条直线上放了一系列机枪兵, 对可爱的狗狗们进行了残忍的屠杀。

已知个机枪兵有一定的射程。然而这是一种特殊的星际版本,机枪升攻击的时候也 可以升射程!所以每个机枪的射程不一定相同。现在一列狗兵排成直线,一次一个机枪兵打掉一定范围内的所有狗狗,每个狗狗有一个编号,每次给出一个杀伤区间, 其中的小狗将会被全部杀掉。最后,让你统计一共杀了多少个。

简而言之,给出数轴上 N 条线段,条线段用两个数表示 A , B(-109<=A<B<=109), 表示从 a 到 b 的一条线段。现在请求出它们覆盖数轴上的多长距离。

输入

第一行:N (N ≤ 20000)。

以后 N 行,行两个数:Ai Bi 。
输出
一个数,表示覆盖长度 。

样例输入

3
2 8
-1 1
5 10

样例输出

10

思路

线段覆盖问题…很明显可以用线段树或者树状数组来做
当然这个题用结构体排序也就可以了(线段树,树状数组写法就…嘿嘿)
将所有线段按右坐标大小降序排序,再遍历
用一个变量m表示当前的位置(初始化为最右的端点位置)
从左到右遍历的过程中有几种情况(a,b表示左右端点)
a m b
a到m这段是被覆盖的,累加,再将m变为a-1
a b m
a到b这段是被覆盖的,累加,再将m变为a-1
m a b
a到b区间已经被统计过了,忽略
最后输出就可以
(如果难以理解可以在纸上模拟一下计算)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
struct node{
    ll a,b;
    bool operator<(const node x)const{
        return b>x.b;
    }
};
node w[maxn];
int main()
{
    int n;
    while(~scanf("%d",&n)){
        ll ans=0,m=0;
        for(int i=1;i<=n;++i){
            scanf("%lld %lld",&w[i].a,&w[i].b);
            --w[i].b;//由于题目对于1-2-3只覆盖两个格子,将右端点减一方便计算
            m=max(w[i].b,m);
        }
        sort(w+1,w+n+1);
        for(int i=1;i<=n;++i){
            m=min(m,w[i].b);
            if(m>=w[i].a){
                ans+=m-w[i].a+1;
                m=w[i].a-1;
            }
        }
        printf("%lld\n",ans);
    }
}
问题 G: 折纸
题目描述

小s很喜欢折纸。

有一天,他得到了一条很长的纸带,他把它从左向右均匀划分为N个单位长度,并且在每份的边界处分别标上数字0~N。

然后小s开始无聊的折纸,每次他都会选择一个数字,把纸带沿这个数字当前所在的位置翻折(假如已经在边界上了那就相当于什么都不做)。

小s想知道M次翻折之后纸带还有多长。

输入

第一行包含两个正整数 N 和 M ,表示纸带的长度和操作的次数。(N ≤ 10^18,M ≤ 3000)。

接下来的一行包含 M 个整数 Di ,其中 Di 表示第 i 次选择的数字。

输出

输出文件只有一个数字,即纸带最后的长度。

样例输入

5 2
3 5

样例输出

2

思路

样例或许看不出什么

5 2
3 2为例(我们向右翻折)(向左翻折同理)
0 1 2 3 4 5
以3为折痕得到
2 1 0
3 4 5 这里其实可以看成是3 4 5 6
再以2为折痕得到
3
2 1 0
4 5 这里其实可以看成是4 5 6
也就是说以3为折痕翻折后,以2为折痕翻折相当于以4为折痕翻折(因为4和2重合了)
可以发现,在折纸的过程中,每次翻折,部分点的位置也会发生变化,由于m很小,可以不停用循环模拟这种变化
比如以x为折痕在x左边的点a会变到右边的b位置,a和b的关系为b-x=x-a化简也就是b=2*x-a
a x b
由此可以知道每一个点的变化位置
那么用变量维护左端点和右端点,遍历折痕点,模拟出点的位置变化,最后得到的左右端点的距离就是纸带的长度

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
ll a[3005];
int main()
{
    ll n,x,l,r;
    int m;
    while(~scanf("%lld %d",&n,&m)){
        l=0,r=n;
        for(int i=1;i<=m;++i){
            scanf("%lld",&a[i]);
        }
        for(int i=1;i<=m;++i){
            for(int j=i+1;j<=m;++j){//坐标变化(从i+1开始是因为之前的折痕已经没有意义了)
                if(a[j]<a[i]){
                    a[j]=a[i]+a[i]-a[j];
                }
            }
            r=max(r,a[i]+a[i]-l);
            l=a[i];
        }
        printf("%lld\n",r-l);
    }
}
问题 H: 加一
题目描述

你的任务是很简单,给你一个非负整数 N,输出 N+1。

唯一的复杂之处在于给出的整数是一个 2~36 进制(包括边界)中一个未知进制的整数。(大家知道的,从 10 开始的数字分别用 A,B,C,……来表示)

因此,你的程序必须按字典序递增输出所有可能的结果。

输入

一行,包含一个由数字 0 至 9 与大写字母 A 至 Z 组成的整数 N,数据保证没有前导零。 (N包含1~200位数字)

输出

输出所有的可能结果,每个结果占一行。

样例输入

9

样例输出

10
A

思路

他给出的整数可以写成abcdef…这种形式(可能是2-36进制中的任意一个)
假设d是abcdef中最大的那个数,那这个整数的进制就不能小于等于d,否则就进位了
我们获取这个整数最大的位数,就知道了这个整数可能的进制
也就是d+1到36进制,仔细想想我们只需要考虑d+1和d+2进制就行
(d+2进制到36进制都是一样的,只需要加一就行,不能进位)
(因为此时最大的数d加一也不满足进位条件d+1 < d+2 <…< 36)
也就是说
末位加一,就只有两种情况,进位或者不进位
如果末尾是最大的就表示可以进位
否则就不能进位
(当然有一种特殊情况,如果最低位的数字是Z就只能进位)
按这个思路写下来代码不短,注意一些细节就行

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
char s[maxn];
int a[maxn],b[maxn];
char ans1[maxn],ans2[maxn];
int main()
{
    ll n,m,ls,jz;
    while(~scanf("%s",s+1)){
        int ls=strlen(s+1),jz=1;
        for(int i=1;i<=ls/2;++i){//字符串转置
            swap(s[i],s[ls-i+1]);
        }
        for(int i=1;i<=ls;++i){//化为数字
            if(s[i]>='A'&&s[i]<='Z'){
                a[i]=s[i]-'A'+10;
            }else{
                a[i]=s[i]-'0';
            }
            b[i]=a[i];
        }
        for(int i=1;i<=ls;++i){//获取每一位上最大的数
            jz=max(jz,a[i]);
        }
        if(jz==a[1]){//如果最低位是最大的就可能进位,也就是可能有两个答案
            int lb=ls,la=ls;
            ++a[1],++b[1],a[la+1]=0,b[lb+1]=0;
            for(int i=1;i<=la;++i){//进位
                if(a[i]>jz){
                    a[i]-=jz+1;
                    ++a[i+1];
                }
            }
            if(a[la+1]!=0){//如果最高位进位了,就把长度加一
                ++la;
            }
            for(int i=la;i>=1;--i){
                if(a[i]>9){
                    ans1[la-i+1]=a[i]+'A'-10;
                }else{
                    ans1[la-i+1]=a[i]+'0';
                }
            }
            for(int i=lb;i>=1;--i){
                if(b[i]>9){
                    ans2[lb-i+1]=b[i]+'A'-10;
                }else{
                    ans2[lb-i+1]=b[i]+'0';
                }
            }
            ans1[la+1]='\0',ans2[lb+1]='\0';//添加字符串结束标志
            if(jz==35){//如果已经是Z那就不能加一只能进位
                printf("%s\n",ans1+1);
            }else if(strcmp(ans1+1,ans2+1)<=0){//按ASCII码排序
                printf("%s\n%s\n",ans1+1,ans2+1);
            }else{
                printf("%s\n%s\n",ans2+1,ans1+1);
            }
        }else{//不能进位,直接加一
            ++b[1],ans2[ls+1]='\0';
            for(int i=ls;i>=1;--i){
                if(b[i]>9){
                    ans2[ls-i+1]=b[i]+'A'-10;
                }else{
                    ans2[ls-i+1]=b[i]+'0';
                }
            }
            printf("%s\n",ans2+1);
        }
    }
}
  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值