2023环翠区编程挑战赛中学组题解

T1. 出栈序列

题目描述

栈是一种“先进后出”的数据结构,对于一个序列 1 , 2 , . . . , n 1,2, ...,n 1,2,...,n,其入栈顺序是 1 , 2 , . . . n 1,2, ...n 1,2,...n,但每个元素出栈的时机可以自由选择。

例如 1 1 1入栈、 1 1 1出栈, 2 2 2入栈、 3 3 3入栈、 3 3 3出栈、 2 2 2出栈,于是出栈序列为 1   3   2 1\ 3\ 2 1 3 2

对于入栈顺序 1   2   . . .   n 1\ 2\ ...\ n 1 2 ... n,出栈序列有很多个,但出栈序列的总数不是 1   2   . . .   n 1\ 2\ ...\ n 1 2 ... n的全排列数,因为有些排列不满足出入栈规则。

例如 3   1   2 3\ 1\ 2 3 1 2就不是一个合法的出栈序列,因为 3 3 3先出栈意味着此时 1 1 1 2 2 2在栈里面,入栈顺序是 1   2 1\ 2 1 2,于是出栈必须是 2   1 2\ 1 2 1

小明仔细研究了出入栈的规则,它发现凡是不合法的出栈序列都有一个规律,至少有一个数,后面比它小的数,存在递增的情况。

现在请你帮助小明编写程序,对于给定的出栈序列,判断是否合法。

输入格式:

第一行 t t t,表示 t t t组数据
对每组数据
第一行 n n n,表示序列长度
第二行 1.. n 1..n 1..n的一个排列,表示要判断的出栈序列

输出格式:

t t t行,每行YesNo,表示出栈序列是否合法

输入样例1:

2
3
1 2 3
3
3 1 2

输出样例1:

Yes
No

数据范围

所有数据: t < = 5 t<=5 t<=5
样例1-3: n < = 100 n<=100 n<=100
样例4-5: n < = 2000 n<=2000 n<=2000
样例6-10: n < = 1 0 5 n<=10^5 n<=105

代码实现

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], stk[N];
int main()
{
    int t;
    scanf("%d", &t);
    while(t --)
    {
        int n, top = 0;
        scanf("%d", &n);
        for(int i = 1; i <= n; i ++) scanf("%d", a + i);
        int in = 1, out; //进栈in,出站out
        for(int i = 1; i <= n; i ++) {
            out = a[i];
            while(in <= out) stk[top ++] = in ++; //将小于等当前入栈元素的所有数据入栈
            if(stk[top - 1] == out) { top --; } //检查栈顶元素是否为要出栈的数据
            else break;
        }
        if(top == 0) puts("Yes");
        else puts("No");
    }
}

领头牛

题目描述

在我国有一处牧场,里面养育着两种牛分别是 H H H牛和 G G G牛。为了方便实时管理我们需要在牛群中选出领头牛,这样我们只需要牵着领头牛其他牛自然就会跟着走。

现在我们要分别选出 H H H领头牛和 G G G领头牛凑成一对。

现在我们让牛群站成一排,例如: G H H G GHHG GHHG ,每头牛都有一个属性“领导能力”。假设第 1 1 1 头牛的领导能力为 2 2 2 ,那么他可以领导包含自身之后的两头牛也就是 “ G H GH GH” 。

但是光有领导能力是不够做领头牛的,领头牛的要求满足两种要求之一:

  • 能够领导自身品种全部的牛;
  • 能够领导对方的领头牛。

我们会给出队伍顺序和每头牛的领导能力,请问有多少对领头牛?

输入格式

第一行输入 n n n 表示牛的数量;
第二行输入 n n n个字符,有 G G G H H H组成,表示牛的排列顺序;
第三行输入 n n n个数,表示牛的领导能力。

输出格式:

输出一个整数 n n n表示可以凑成几对领导牛。

输入样例1:

4
GHHG
2 2 1 1

输出样例1:

1

样例1说明:

第一头牛 G G G,可以领导 2 2 2头牛“ G H GH GH”,没有包含全部的 G G G牛,但是第二头牛 H H H可以领导 2 2 2头牛“ H H HH HH”,包含了所有的 H H H牛,所以第二头 H H H牛是领头牛,那么第一头 G G G牛包含了对方的领头牛,所以第一头 G G G牛也是领头牛。
现在就是 G G G有一头领头牛, H H H有一头领头牛,那么只能凑一对 ( 1 , 2 ) (1,2) (12)

输入样例2:

3
GGH
2 2 1

输出样例2:

2

样例2说明:

第一头牛 G G G,领导 2 2 2头牛“ G G GG GG”包含全部的 G G G牛,所以是领头牛;
第二头牛 G G G,领导 2 2 2头牛“ G H GH GH”包含对方领头牛,所以是领头牛;
第三头牛 H H H,领导 1 1 1头牛“ H H H”包含全部H牛,所以是领头牛;
配对 ( 1 , 3 ) (1,3) 13 ( 2 , 3 ) (2,3) 23所以有 2 2 2

数据范围:

对于20%的数据: 1 ≤ n ≤ 20 1 ≤ n ≤ 20 1n20
对于40%的数据: 1 ≤ n ≤ 100 1 ≤ n ≤ 100 1n100
对于60%的数据: 1 ≤ n ≤ 5000 1 ≤ n ≤ 5000 1n5000
对于80%的数据: 1 ≤ n ≤ 20000 1 ≤ n ≤ 20000 1n20000
对于100%的数据: 1 ≤ n ≤ 1 0 5 1 ≤ n ≤ 10^5 1n105。​​

代码实现(80分)

模拟,时间复杂度为 O ( n 2 ) O(n^2) O(n2)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e5 + 10;
char s[N];
int a[N], g[N], h[N];
int main()
{
    int n, x;
    cin >> n;
    cin >> s + 1;
    int s1 = 0, s2 = 0;
    for(int i = 1; i <= n; i ++)
    {
        if(s[i] == 'G') s1 ++;
        else s2 ++;
    }
    for(int i = 1; i <= n; i ++) 
    {
        cin >> x;
        a[i] = i + x - 1; //a[i]保存领导能力到达的位置
    }
    //处理第一种领导者:其记录的名单上包含它的品种的所有奶牛
    for(int i = 1; i <= n; i ++)
    {
        int gg = 0, hh = 0;
        for(int j = i; j <= a[i]; j ++)
        {
            if(s[j] == 'G') gg ++;
            else hh ++;
        }
        if(s[i] == 'G' && gg == s1) g[i] = 1; //标记为领导者
        else if(s[i] == 'H' && hh == s2) h[i] = 1; //标记为领导者
    }
    //处理第二种领导者:其记录的名单上记录了另一品种奶牛的“领导者”。
    for(int i = 1; i <= n; i ++)
    {
       
       for(int j = i; j <= a[i]; j ++)
       {
           if(s[i] == 'G' && h[j] == 1)
           {
               g[i] = 1;
               break;
           }
           else if(s[i] == 'H' && g[j] == 1)
           {
               h[i] = 1;
               break;
           }
       }
    }
    int c1 = 0, c2 = 0;
    for(int i = 1; i <= n; i ++)
        if(g[i]) c1 ++;
    for(int i = 1; i <= n; i ++)
        if(h[i]) c2 ++;
    cout << c1 * c2 << endl;
}

代码实现(100分)

朴素做法的问题在于使用了两层循环,其实第二层循环可以使用前缀和进行优化,用空间换时间。

  • s1[]前缀和数组,s1[i]表示前i个字符中字符G的个数
  • s2[]前缀和数组,s2[i]表示前i个字符中字符H的个数
  • gs[]前缀和数组,gs[i]表示前i头牛中第一类G品种领导者的数量
  • hs[]前缀和数组,hs[i]表示前i头牛中第一类H品种领导者的数量
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e5 + 10;
char s[N];
int a[N], s1[N], s2[N], g[N], h[N], gs[N], hs[N];
int main()
{
    int n;
    cin >> n;
    cin >> s + 1;
    for(int i = 1; i <= n; i ++) cin >> a[i];
    
    //s1[i]前缀和, 表示前i个字符中字符G的个数
    //s2[i]前缀和, 表示前i个字符中字符H的个数
    for(int i = 1; i <= n; i ++)
    {
        s1[i] = s1[i - 1], s2[i] = s2[i - 1];
        if(s[i] == 'G') s1[i] ++;
        else s2[i] ++;
    }
    
    //处理第一种领导者:其记录的名单上包含本身的品种的所有奶牛
    for(int i = 1; i <= n; i ++)
    {
        //gg表示从i到a[i]位置所有G的个数
        //hh表示从i到a[i]位置所有H的个数
        int R = min(i + a[i] - 1, n); //注意结束位置不能超过n
        int gg = s1[R] - s1[i - 1], hh = s2[R] - s2[i - 1];
        //其记录的名单上包含本身的品种的所有奶牛
        if(s[i] == 'G' && gg == s1[n]) g[i] = 1; //将i位置标记为'G'的领导者
        else if(s[i] == 'H' && hh == s2[n]) h[i] = 1;//将i位置标记为'H'的领导者
    }
    
    //gs[i]前缀和,表示前i个奶牛中G的第一种领导者个数
    //hs[i]前缀和,表示前i个奶牛中H的第一种领导者个数
    for(int i = 1; i <= n; i ++)
    {
        gs[i] = gs[i - 1], hs[i] = hs[i - 1];
        if(g[i]) gs[i] ++;
        if(h[i]) hs[i] ++;
    }    
    //处理第二种领导者:其记录的名单上记录了另一品种奶牛的“领导者”。
    for(int i = 1; i <= n; i ++)
    {  
        //其记录的名单上记录了另一品种奶牛的领导者
        int R = min(i + a[i] - 1, n); //注意结束位置不能超过n
        if(s[i] == 'G' && (hs[R] - hs[i - 1]) != 0) g[i] = 1;
        else if(s[i] == 'H' && (gs[R] - gs[i - 1]) != 0) h[i] = 1;
    }
   	//c1表示G的领导者个数,c2表示H的领导者个数
	int c1 = 0, c2 = 0;
    for(int i = 1; i <= n; i ++)
        if(g[i]) c1 ++;
    for(int i = 1; i <= n; i ++)
        if(h[i]) c2 ++;
    cout << c1 * c2 << endl;
}


两端对齐

题目描述

小明最近在用软件写文档时发现,在英文内容的键入时,软件会自动添加一些额外的空格到内容中,使得整段英文在文本区域的两端对齐,并且不会出现单个单词被拆分到两行的情况,看上去整齐又美观。

现在给定一篇文章中的 n n n个单词,请试着将其按顺序以“两端对齐”的方式输出,要求:

  • 单词间至少由一个空格隔开,行末单词后无空格
  • 各行字符数均为 m m m个,必要时可以在单词间补充额外的空格
  • 一行内单词间的空格数要尽量相同,若始终不能均匀分配,那么左侧的空格数可以多一些
  • 输出行数要尽可能少一些,也即每行放置的单词要尽可能多一些
    最后一行要左对齐,单词间不再插入额外的空格,末尾补空格至 m m m个字符

输入格式:

第一行输入两个数 n n n m m m
接下来 n n n行,每行 1 1 1个单词 s ​ i s_​i si​​ ,均由小写字母构成

输出格式:
输出排版后的文章(数据保证有解)

输入样例:

10 14
to
be
or
not
to
be
that
is
a
question

输出样例:

to  be  or not
to  be that is
a question

样例说明:

第1行在前两个单词后分别额外添加了一个空格;
第2行在第一个单词后额外添加了一个空格;
最后一行在末尾额外添加了4个空格。
各行均为 14 14 14个字符,输出的换行符不算入 m m m

数据范围:

对于30%的数据: 1 ≤ n ≤ 100 1\le n \le100 1n100
对于60%的数据: 1 ≤ n ≤ 1000 1\le n \le1000 1n1000
对于100%的数据: 1 ≤ n ≤ 1 0 4 1\le n \le10^4 1n104 1 ≤ m ≤ 1 0 6 1\le m \le10^6 1m106 1 ≤ ∣ s i ∣ ≤ 100 1\le|s_i|\le100 1si100

代码实现(模拟)

时间复杂度 O ( n 2 ) O(n^2) O(n2)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e4 + 10;
string a[N];
int main()
{
    int n, m;
    cin >> n >> m;
    for(int i = 0; i < n; i ++) cin >> a[i];
    string s = a[0]; //保存每行由单词和一个空格组成的字符串
    int cnt = 1; //每行单词的数量
    for(int i = 1; i < n; i ++)
    {
        int len = s.size() + 1 + a[i].size(); //每行单词的长度
        if(len <= m) { s = s + ' ' + a[i]; cnt ++; }//每行字符串的长度不超过m
        else { //加入新单词后长度超过m            
            int b = m - s.size(); //需要填补空格
            int k = cnt - 1; //除了结尾的单词,还有有k个单词。在除了结尾单词的右边补充空格,不能计算结尾单词
            int c = k != 0 ? b / k : 0; //平均每个单词需要额外添加的空格数量。如果除了结尾只有1个单词,那就不需要补充空格
            int r = k != 0 ? b % k : 0;; //剩余需要在右侧补充的空格数量
            for(int j = i - cnt; j < i; j ++) // 输出单词
            {
                if(j == i - 1) cout << a[j]; //本行最后一个单词右边不添加空格
                else 
                {
                    cout << a[j] << " "; //不是本行最后一个单词,默认添加一个空格
                    for(int k = 0; k < c; k ++) cout << " "; //输出平均的空格
                    if(r != 0) { //输出需要右侧补充的剩余空格,每个单词只补一个
                        cout << " ";
                        r --;
                    }
                }
            }
            cout << endl;
            s = a[i];
            cnt = 1;
        }
    }
    
    while(s.size() < m) s = s + " "; //最后一行末尾补空格
    cout << s << endl; //输出最后一行的单词
}

T4. 免费超市

题解在我的另一篇博客,环翠区中小学生编程挑战赛题解中学组T4:免费超市

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少儿编程乔老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值