2021年春季ACM训练赛第1场

问题 A: 逆序五进制

题目描述
编写一个程序,首先将一个十进制正整数逆序【需要去掉前导0】,然后转换成五进制正整数,最后输出该五进制正整数。

输入
每组测试数据的输入占一行,输入一个十进制正整数n。 (n<=100000)

输出
每组测试数据的输出占一行,输出转换后所得的五进制正整数。

样例输入

1000

样例输出

1

分析:模拟题,先去除前导0,然后逆序,最后转化为五进制。
代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int fun(int n){
    
    //倒序
    int sum=0;
    while(n){
    
    
        sum*=10;
        sum+=n%10;
        n/=10;
    }
    return sum;
}
int f(int n){
    
    //转换成五进制
    int sum=0,x=1;
    while(n){
    
    
        sum+=n%5*x;
        n/=5;
        x*=10;
    }
    return sum;
}
int main(){
    
    
    int n;
    while(~scanf("%d",&n)){
    
    
        while(n%10==0)n/=10;//去除前导0
        n=fun(n);
        cout<<f(n)<<endl;
    }
    return 0;
}

问题 B: 斐波那契蛇

题目描述
明明昨晚做了一个梦。在梦里,很多很多斐波那契数连成了一条蛇。突然,最大的那个数变成了蛇头,把明明一口给吞到肚子里去了。
明明被吓醒了。他赶紧拿笔在纸上面画了一条斐波那契蛇。
34 21 13
1 1 8
2 3 5
这是一个蛇形迂回的斐波那契数列,它是一个n*n的矩阵,在上面的矩阵中n=3。第1行第1列是最大值,然后按照顺时针的次序数字逐渐变小。
下面是n=4时的情况:
987 610 377 233
5 3 2 144
8 1 1 89
13 21 34 55
明明希望你能够编写一个程序,输入一个正整数n,然后逐行逐列输出斐波那契蛇形矩阵中的元素。

输入
每组输入数据占一行,包含一个正整数n,表示斐波那契蛇形矩阵的大小。(n<10)

输出
每组输出数据占一行,逐行逐列(从第1行开始到第n行,每一行从第1列开始到第n列)输出斐波那契蛇形矩阵中的元素,每两个数字之间用一个空格隔开。

样例输入

3

样例输出

34 21 13
1 1 8
2 3 5

分析:模拟题,注意开ll,将整个矩阵模拟出来即可。详细看代码。
代码:

#include <bits/stdc++.h>
#define ll long long
#define INF 999999999
using namespace std;
ll f[105];
void fun(int n){
    
    
    ll mp[15][15];
    int u=1,b=n,r=n,l=1,id=n*n;//上下右左
    while(u<=b&&l<=r){
    
    
        for(int i=l;i<=r;i++)mp[u][i]=f[id--];
        u++;
        if(u>b&&l>r)break;
        for(int i=u;i<=b;i++)mp[i][r]=f[id--];
        r--;
        if(u>b&&l>r)break;
        for(int i=r;i>=l;i--)mp[b][i]=f[id--];
        b--;
        if(u>b&&l>r)break;
        for(int i=b;i>=u;i--)mp[i][l]=f[id--];
        l++;
        if(u>b&&l>r)break;
    }
    for(int i=1;i<=n;i++){
    
    
        for(int j=1;j<=n;j++){
    
    
            if(j!=n)cout<<mp[i][j]<<" ";
            else cout<<mp[i][j];
        }
        cout<<endl;
    }
}
int main(){
    
    
    int n;
    f[1]=1,f[2]=1;
    for(int i=3;i<=105;i++)f[i]=f[i-1]+f[i-2];//预处理斐波那契数列
    while(~scanf("%d",&n)){
    
    
        fun(n);
    }
    return 0;
}

问题 C: 扑克牌接龙游戏

题目描述
小明最近喜欢上了一种非常简单的扑克牌接龙游戏。
游戏的玩法是这样的:
将扑克牌洗牌后平均分为两堆,每人轮流出一张牌进行接龙。如果某个人出的牌的大小和接龙牌堆中一张已有牌的大小相同(不考虑花色),那么他可以将这两张牌以及中间所有的牌全部收走并据为己有。例如:如果在接龙的牌堆中有一个3,你再出一个3,那么这两个3以及它们中间的牌都归你所有。

两个人依次出牌,最后比谁收到的牌最多即可获胜。

我们把这个问题稍作简化,如果是一个人在玩这个游戏。现在给你一串数字和字母表示扑克牌的次序,请问最多的一次可以收走多少张牌。

输入
单组输入,输入占两行。
第1行是牌的总数n。(n<=1e5)
第2行输入n个数字或者字符(对应2、3、4、5、6、7、8、9、10和A、J、Q、K),两两之间用空格隔开。

输出
输出一个整数,即最多一次可以收走的牌数。

样例输入

11
A 2 3 3 4 3 2 2 3 4 A

样例输出

5

提示
样例解释:当出第4张“3”时,可以收走第3张“3”和第4张“3”,共收走2张牌;当出第7张“2”时,可以收走第2张“2”以及第5张到第7张牌,即一共收走了“2,4,3,2”,共收走4张牌;当出第11张“A”时,可以收走第1张“A”和第8张到第11张牌,即一共收走了“A,2,3,4,A”,共收走5张牌。因此最多的一次收走5张牌。

分析:
有更简单的方法,我这个太暴力了,往下翻
用两个map分别记录本张牌上一次出现的位置,map初始化是0,mp1记录不拿牌的位置,也就是该牌的本来位置,mp记录拿完牌之后的位置,num标记拿完牌后的位置。比如说 A 2 3 4 3 5,mp[‘A’]=mp1[‘1’]=1,mp[‘2’]=mp1[‘2’]=2,mp[‘3’]=mp1[‘3’]=3,mp[‘4’]=mp1[‘4’]=3,下一张牌是3,那么我们将拿走三张牌,num变成3(代表拿完牌后下一张牌是第3张),同时将上一个3出现的下标位置mp1[‘3’]到本次3的位置i之间的mp和mp1之间的所有牌都置为0,因为这之间的牌都被拿走了。
写了另外一个例子:

在这里插入图片描述
好像我的语言表达不是很清楚,具体看代码。

修改之后的方法,前面那种太暴力了,基本思想是没有变的,只不过将牌抽出来之后的操作优化了。将每一张牌用一个数字表示。pos数组记录这张牌的上一个位置,t记录当前是第几张牌。每次拿完牌,就更新t为新的下一张牌的位置。如果这张牌x没有出现过(pos[x]=0),那么给pos[x]赋值为t。如果pos[x]不为0,如果上一张牌出现的位置是不是比t大,说明这张牌已经被收走了,那么给pos[x]赋值为t。如果pos[x]比t小,但是pos[x]位置的数到底不是x说明也被收走了。上述三种情况都不满足才说明pos[x]位置的牌就是x,那么此时更新一下ma,维护拿走的最大牌数。可以参考代码在纸上手写一下。

#include <bits/stdc++.h>
#define ll long long
#define INF 999999999
using namespace std;
char s[5];
int num[100005],pos[40];
int main(){
    
    
    int n,ma=0;scanf("%d",&n);
    int t=1,x;
    for(int i=1;i<=n;i++){
    
    
        scanf("%s",s);
        x=num[t]=s[0]-'0';
        if(pos[x]&&pos[x]<t&&num[pos[x]]==x)ma=max(ma,t-pos[x]+1),t=pos[x];
        else pos[x]=t++;
    }
    printf("%d\n",ma);
    return 0;
}

问题 D: 小h的数列

题目描述
有一个长度为n的数列,初始每个位置的数字都是0,进行m次操作,每次操作将一个区间的数字加上某个特定的数字,输出最终的数列

输入
第一行两个数字n和m表示有长度为n的数列,有m次操作
后m行,每行三个正整数a,b,c表示将区间[a,b]的数字加上c
(n<=100000,m<=100000,c<=100000)

输出
最终的数列

样例输入

5 2
1 3 1
2 5 2

样例输出

1 3 3 2 2

分析: 差分前缀和
前缀和,又称和数组,用于求解获取某一部分连续区间和的问题。
差分,又称差分数组,用于对某一段连续区间中所有的元素进行批量处理
算法实现:首先填满差分数组,对区间[a,b] 进行批量处理:ans[a]+=c,ans[b+1]-=c,用于对[a,b]进行+c操作,最后对差分数组进行前缀和
a,b的区间加c操作,只有a,b之间的数字加了c,a左边和a右边的数字没有变化,我们维护的ans差分数组是第i个数和第i-1个数之间的差值。最后进行前缀和操作即可。

emmm没看懂我这里说的同学可以百度一下差分前缀和。
在这里插入图片描述

代码:

#include <bits/stdc++.h>
#define ll long long
#define INF 999999999
using namespace std;
ll ans[100005],sum[100005];
int main(){
    
    
    int n,m;scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++){
    
    
        int a,b,c;scanf("%d %d %d",&a,&b,&c);
        ans[a]+=c;
        ans[b+1]-=c;
    }
    for(int i=1;i<=n;i++)sum[i]+=ans[i]+sum[i-1],cout<<sum[i]<<" ";
    cout<<endl;
    return 0;
}

问题 E: 李华上大学了吗?(I)

题目描述
小波虽然早已高中毕业了,但是每逢高考季都会不由自主地关注高考动态,也是第一时间知道了2020高考英语卷中的不见了当年的老朋友:李华。小波不禁感叹到:青结了!
现给出一个长字符串s,其中包括大小写字母,符号,空格等,问其中是否包含字符串"Did Li Hua go to university"(不含引号)
如果包含则输出"Yes",否则输出"No"(不含引号)。

输入
第一行一个整数t,代表t组数据。
接下来t行,每行一字符串s,其中包括大小写字母,符号,空格等,长度小于1e5 。

输出
输出t行,每行如果字符串中包含"Did Li Hua go to university"(不含引号),则输出"Yes",否则输出"No"(不含引号)。

样例输入

2
Did Li Hua go to university,Xiaobo?
Did you go to university,Xiaobo?

样例输出

Yes
No

分析: 直接用string的find函数即可。
代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int main(){
    
    
    int t;scanf("%d",&t);
    getchar();
    while(t--){
    
    
        string s;getline(cin,s);
        if(s.find("Did Li Hua go to university")<s.size())cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }
    return 0;
}

问题 F: 李华上大学了吗?(II)

题目描述
李华正在筹划他的毕业旅行。他打算去找他的外国笔友们去面基,首先第一站是法国巴黎,但是去巴黎的路线有很多,交通工具也有很多可供选择。
现有一结点数为n,边的条数为m的无向图表示李华到巴黎的所有路线。其中李华的家为结点s,巴黎为结点e,李华出发时间为start,问李华最早什么时候能到达巴黎?

输入
第一行两个整数:结点数n,边数m(n<=1000,m<=10000)
第二行到第m+1行每行各有三个整数:结点u,结点v,需要的时间time(1<=u,v<=n,time<50,time以小时为单位)
最后一行为李华家的位置:s,巴黎的位置:e,出发时间start(1<=s,e<=n,出发时间格式为month.day/hour,小时为24小时制,出发年份默认为2020年,且一定会在2020年到达)

输出
李华最早能到达巴黎的时间eTime(格式与出发时间格式相同)

样例输入

4 4
1 2 5
1 3 6
2 4 8
3 4 6
1 4 7.9/8

样例输出

7.9/20

提示
李华选择了1-3,3-4这条路线。

分析: 最短路板子题,注意最后的日期转换即可。
代码:

#include <bits/stdc++.h>
#define ll long long
#define INF 999999999
using namespace std;
int mp[1005][1005],s,e,vis[1005],dis[1005];
int months[]={
    
    0,31,29,31,30,31,30,31,31,30,31,30,31};
string start;
int n,m;
void dijkstra()
{
    
    
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)dis[i]=mp[s][i];
    vis[s]=1;dis[s]=0;
    for(int i=1;i<n;i++){
    
    
        int j=-1,Min=INF;
        for(int k=1;k<=n;k++){
    
    
            if(!vis[k]&&dis[k]<Min){
    
    
                j=k;
                Min=dis[k];
            }
        }
        vis[j]=1;
        for(int k=1;k<=n;k++)
            if(!vis[k]&&dis[j]+mp[j][k]<dis[k])
                dis[k]=dis[j]+mp[j][k];
    }
    int h=dis[e];
    //cout<<h<<endl;
    int month=0,day=0,hour=0,i;
    for(i=0;i<start.size();i++){
    
    
        if(start[i]=='.')break;
        month*=10;
        month+=start[i]-'0';
    }i++;
    for(;i<start.size();i++){
    
    
        if(start[i]=='/')break;
        day*=10;
        day+=start[i]-'0';
    }i++;
    for(;i<start.size();i++){
    
    
        hour*=10;
        hour+=start[i]-'0';
    }
    //cout<<month<<" "<<day<<" "<<hour<<endl;
    hour+=h;
    if(hour>=24)day+=hour/24,hour%=24;
    while(day>months[month])day-=months[month],month++;
    cout<<month<<"."<<day<<"/"<<hour<<endl;
}
int main(){
    
    
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            mp[i][j]=INF;
    for(int i=1;i<=m;i++){
    
    
        int u,v,time;scanf("%d %d %d",&u,&v,&time);
        mp[u][v]=mp[v][u]=min(time,mp[u][v]);
    }
    scanf("%d %d",&s,&e);
    cin>>start;
    dijkstra();
    return 0;
}

问题 G: 李华上大学了吗?(III)

题目描述
李华顺利地到达了巴黎,他的笔友Peter带他开启了他的巴黎之旅。
途中,李华遇到了许多心动的纪念品想要带回家,但是他又不想自己太累,而且他买纪念品也有相应的预算k,现给出他心动的纪念品清单:共有n件,其中每件都各有其价格price,重量weight,心动值v(其中心动值为1~5之间的数值),需要注意的是:在心动值不同的情况下,李华会优先选择心动值大的纪念品;若心动值相同,李华会优先选择比较便宜的纪念品,具体见样例。同时给出李华在保证不累的情况下,最多能拿的物品重量m。在不超过预算并且保证不累的情况下,李华最多可以带几件纪念品回家?

输入
第一行三个整数,分别为:纪念品件数n,最多能拿的物品重量m,预算k。(n<1e5,m<100,k<10000,k的单位为元,m的重量为kg)
第二行到第n+1行,分别为每件物品的价格price,重量weight,心动值v。(price<10000,weight<100,v为1~5之间的整数,price的单位为元,weight的重量为kg)

输出
在不超过预算并且保证不累的情况下,李华最多可以带回家的纪念品件数。

样例输入

3 10 1000
100 5 3
50 3 2
300 3 3

样例输出

2

提示
由于李华会优先选择心动值大的物品,所以李华选择了第一件和第三件物品。

分析: 乍一看以为是背包题,其实就是考察如何排序,先按心动值降序排列,再按价值升序排列,最后按重量升序排列。

代码:

#include <bits/stdc++.h>
#define ll long long
#define INF 999999999
using namespace std;
struct node{
    
    
    int p,w,v;
}s[100005];
int cmp(const node &a,const node &b){
    
    
    if(a.v!=b.v)return a.v>b.v;
    else if(a.p!=b.p)return a.p<b.p;
    else return a.w<b.w;
}
int main(){
    
    
    int n,m,k;scanf("%d %d %d",&n,&m,&k);
    for(int i=1;i<=n;i++){
    
    
        int p,w,v;scanf("%d %d %d",&p,&w,&v);
        s[i]=((node){
    
    p,w,v});
    }
    sort(s+1,s+1+n,cmp);
    int sum=0;
    for(int i=1;i<=n;i++){
    
    
        if(s[i].w<=m&&s[i].p<=k)m-=s[i].w,k-=s[i].p,sum++;
    }
    cout<<sum<<endl;
    return 0;
}

问题 H: 消灭病毒

题目描述
小明最近喜欢上了一款“消灭病毒”的游戏。游戏中有各种各样不同的病毒,消灭的病毒越多,得分越高。但是在游戏中,玩家一旦被病毒击中将减少一定的生命值。
在游戏中,玩家可以通过添加不同的药水来获取生命值。生命值越高,在游戏中存活的时间越长。
游戏一共提供了N种药水,第i种药水的体积为V(i),补充的生命值为L(i)。
玩家可以携带一个体积为V的药水瓶。每一种药水可以全部装入药水瓶,也可以只装入一部分。当然,如果你装入一部分药水,那么也只能补充这一部分药水按比例对应的生命值。
请问如何装入药水可以使得初始的生命值最大?请输出最大的初始生命值。

输入
单组输入。
第1行输入两个正整数V和N,分别表示药水瓶的体积和药水的种类数。(N<=100)
接下来N行,每行包含两个正整数,分别对应第i种药水的体积V(i)和生命值L(i)。

输出
输出最大的初始生命值(四舍五入保留两位小数)。

样例输入

10 3
5 6
3 3
4 2

样例输出

10.00

分析: 部分背包板子题。

代码:

#include <bits/stdc++.h>
#define ll long long
#define INF 999999999
using namespace std;
struct node{
    
    
    double v,l,c;
}s[105];
int cmp(const node &a,const node &b){
    
    
    return a.c>b.c;
}
int main(){
    
    
    int v,n;scanf("%d %d",&v,&n);
    for(int i=1;i<=n;i++){
    
    
        cin>>s[i].v>>s[i].l;
        s[i].c=s[i].l/s[i].v;
    }
    sort(s+1,s+1+n,cmp);
    double sum=0;
    for(int i=1;i<=n;i++){
    
    
        if(s[i].v<=v)v-=s[i].v,sum+=s[i].l;
        else{
    
    
            sum+=s[i].c*v;
            break;
        }
    }
    printf("%.2f\n",sum);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/ladiez/article/details/115282115