第十一届蓝桥杯省赛第一场C++AB组真题

发布时间 2023-03-22 21:13:54作者: 勇敢龙龙

第十一届蓝桥杯省赛第一场C++A/B组真题

整除序列

有一个序列,序列的第一个数是 $ n $,后面的每个数是前一个数整除 $ 2 $,请输出这个序列中值为正数的项。

输入格式

输入一行包含一个整数 $ n $。

输出格式

输出一行,包含多个整数,相邻的整数之间用一个空格分隔,表示答案。

数据范围

$ 1 \le n \le 10^{18} $

输入样例:

20

输出样例:

20 10 5 2 1

直接暴力模拟,没啥好说的

#include <bits/stdc++.h>
using namespace std;
long long n;
int main()
{
    cin>>n;
    cout<<n<<" ";
    while (n/2)
    {
        n/=2;
        cout<<n<<" ";
    }
    
    return 0;
}

解码

小明有一串很长的英文字母,可能包含大写和小写。

在这串字母中,有很多连续的是重复的。

小明想了一个办法将这串字母表达得更短:将连续的几个相同字母写成字母 + 出现次数的形式。

例如,连续的 $ 5 $ 个 $ a $,即 $ aaaaa $,小明可以简写成 $ a5 $(也可能简写成 $ a4a \(、\) aa3a $ 等)。

对于这个例子:$ HHHellllloo $,小明可以简写成 $ H3el5o2 $。

为了方便表达,小明不会将连续的超过 $ 9 $ 个相同的字符写成简写的形式。

现在给出简写后的字符串,请帮助小明还原成原来的串。

输入格式

输入一行包含一个字符串。

输出格式

输出一个字符串,表示还原后的串。

数据范围

输入字符串由大小写英文字母和数字组成,长度不超过 $ 100 $。
请注意原来的串长度可能超过 $ 100 $。

输入样例:

H3el5o2

输出样例:

HHHellllloo

也是直接模拟即可

#include <bits/stdc++.h>
using namespace std;
string s,ans;
int main()
{
    cin>>s;
    for (int i=0;i<s.size();i++)
    {
        if (s[i]>='0'&&s[i]<='9')
            for (int j=0;j<s[i]-'0'-1;j++)
                ans+=s[i-1];
        else ans+=s[i];
    }
    
    cout<<ans<<endl;
    return 0;
}

走方格

在平面上有一些二维的点阵。

这些点的编号就像二维数组的编号一样,从上到下依次为第 $ 1 $ 至第 $ n $ 行,从左到右依次为第 $ 1 $ 至第 $ m $ 列,每一个点可以用行号和列号来表示。

现在有个人站在第 $ 1 $ 行第 $ 1 $ 列,要走到第 $ n $ 行第 $ m $ 列。

只能向右或者向下走。

注意,如果行号和列数都是偶数,不能走入这一格中。

问有多少种方案。

输入格式

输入一行包含两个整数 $ n, m $。

输出格式

输出一个整数,表示答案。

数据范围

$ 1 \le n,m \le 30 $

输入样例1:

3 4

输出样例1:

2

输入样例2:

6 6

输出样例2:

0

经典的摘花生、机器人走方格等\(DP\)问题

#include <bits/stdc++.h>
using namespace std;
const int N = 35;
int n,m;
int f[N][N];
int main()
{
    cin>>n>>m;
    for (int i=1;i<=n;i++) f[i][1]=1;
    for (int j=1;j<=m;j++) f[1][j]=1;
    
    for (int i=2;i<=n;i++)
        for (int j=2;j<=m;j++)
        {
            if (i%2==0&&j%2==0) continue;
            f[i][j]=f[i-1][j]+f[i][j-1];
        }
    cout<<f[n][m]<<endl;
    return 0;
}

整数拼接

给定一个长度为 $ n $ 的数组 $ A_1, A_2, · · · , A_n $。

你可以从中选出两个数 $ A_i $ 和 $ A_j \((\) i $ 不等于 $ j $),然后将 $ A_i $ 和 $ A_j $ 一前一后拼成一个新的整数。

例如 $ 12 $ 和 $ 345 $ 可以拼成 $ 12345 $ 或 $ 34512 $。

注意交换 $ A_i $ 和 $ A_j $ 的顺序总是被视为 $ 2 $ 种拼法,即便是 $ A_i = A_j $ 时。

请你计算有多少种拼法满足拼出的整数是 $ K $ 的倍数。

输入格式

第一行包含 $ 2 $ 个整数 $ n $ 和 $ K $。

第二行包含 $ n $ 个整数 $ A_1, A_2, · · · , A_n $。

输出格式

一个整数代表答案。

数据范围

$ 1 \le n \le 10^5 \(, \) 1 \le K \le 10^5 \(, \) 1 \le A_i \le 10^9 $

输入样例:

4 2
1 2 3 4

输出样例:

6

暴力

暴力是比较容易想的,二重循环,每次判断两个数拼接起来是否符合条件即可。显然复杂度达到了\(O(n^2)\)

优化

超时原因就是我们在确定一个数\(A_i\) 后,确定\(A_j\) 时采用了枚举。那么优化就是可以从如何确定答案的方式上想了。

本质上,本题就是求解A[i]A[j] 拼接后即(A[i]*10^len(A[j])+A[j])%k==0 ,即符合上述等式的个数。

那么我们稍微移项一下化简为:(A[i]*10^len(A[i))%k==-A[j]%k

那么此时我们就可以发现,可以哈希记录答案个数了。

因此,创建一个二维数组cnt[i][j],这个数组就表示(A[j] 10 ^ i)% k == j的数量;
所以本题就转换为每次枚举一个Ai,然后计算出其%k的数来,然后再算出Ai的位数len.
然后去寻找cnt[len][Ai % k]的大小,就能得知有多少个数满足条件,同时也就是答案数。

我们会发现有几个小问题和细节需要处理

  • 我们一定要保持逻辑清晰,即首先是只枚举一遍A[i],先不管对应的A[j] 是什么,对于每个A[i],都求A[i]*10^(len)%k,并记录结果,其中len是0~10。第二遍枚举A数组,其实是枚举的A[j],其对于每一个A[j],我们要看一下cnt[len(A[j])][(-A[j]%k+k)%k] 是多少并累加到答案中去。
  • 当然这里我们发现等式的右侧是-A[i]%k 显然会是负数,但我们还是要把这个结果映射为整数才能存在二维数组的第二维中,即答案加上k再Mod k。即(-A[i]%k+k)%k
  • 我们会发现上述操作,显然是没有考虑到A[i]和A[j]是不同的数(即\(i \ne j\))。那么就需要在统计答案的时候,将同时选用A[i]和A[i]的情况去掉。其实并不是很复杂,即最后在统计答案的过程中,对于当前的A[j],求解一下A[j]*10^len(A[j])%k 是否等于(-A[j]%k+k)%k ,如果等于就说明取的两个数是同一个数,去掉即可。

暴力实现,过4个样例

#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int a[N];
int n,k;
typedef long long ll;
ll ans;
bool check(int a,int b)  // 判断(a*10*len(b))%k 是否等于 (-b%k+k)%k
{
    int len = to_string(b).size();
    ll t = a%k;
    while (len--)
        t = t*10%k;
    if (t==(-b%k+k)%k)
        return true;
    return false;
}
int main()
{
    scanf("%d%d",&n,&k);
    for (int i=0;i<n;i++)
        scanf("%d",&a[i]);
    for (int i=0;i<n;i++)
        for (int j=0;j<n;j++)
        {
            if (i==j) continue; // 选择不同的两个数
            if (check(a[i],a[j]))ans++;
        }
    
    printf("%lld\n",ans);
    return 0;
}

哈希记录答案,优化

#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int a[N];
int cnt[10][N];
int n,k;
typedef long long ll;
ll ans;
int main()
{
    cin>>n>>k;
    for (int i=0;i<n;i++) scanf("%d",&a[i]);
    
    for (int i=0;i<n;i++)
    {
        ll t = a[i]%k;
        for (int j=0;j<10;j++)
        {
            cnt[j][t]++;
            t=t*10%k;
        }
    }
    
    for (int j=0;j<n;j++)
    {
        ll t = a[j]%k;
        int len = to_string(a[j]).size();
        ans+=cnt[len][(-t%k+k)%k];
        
        ll r = t;
        while (len--) r=r*10%k;
        if (r==(-t%k+k)%k) ans--;
    }
    
    printf("%lld\n",ans);
    return 0;
}

总结

  • 还是先想朴素的做法,再看超时的原因卡在哪一步
  • 这道题感觉就很经典,既然超时因为是我们在判断条件时想同时兼顾到\(A_i\)\(A_j\) ,那必然就是\(O(n^2)\) 的复杂度。但是如果先只考虑\(A_i\),让\(A_i\) 生成全部的可能并用哈希表记录,再只考虑\(A_j\),看前面生成的哪些是符合条件的。
  • 这就非常巧妙地从“同时限制i和j”变成了“先只考虑i”,“再只考虑j的同时,去检查前面的i哪些合法”

网络分析

小明正在做一个网络实验。

他设置了 $ n $ 台电脑,称为节点,用于收发和存储数据。

初始时,所有节点都是独立的,不存在任何连接。

小明可以通过网线将两个节点连接起来,连接后两个节点就可以互相通信了。

两个节点如果存在网线连接,称为相邻。

小明有时会测试当时的网络,他会在某个节点发送一条信息,信息会发送到每个相邻的节点,之后这些节点又会转发到自己相邻的节点,直到所有直接或间接相邻的节点都收到了信息。

所有发送和接收的节点都会将信息存储下来。

一条信息只存储一次。

给出小明连接和测试的过程,请计算出每个节点存储信息的大小。

输入格式

输入的第一行包含两个整数 $ n, m $,分别表示节点数量和操作数量。

节点从 $ 1 $ 至 $ n $ 编号。

接下来 $ m $ 行,每行三个整数,表示一个操作。

  • 如果操作为 1 a b,表示将节点 $ a $ 和节点 $ b $ 通过网线连接起来。当 a = b 时,表示连接了一个自环,对网络没有实质影响。
  • 如果操作为 2 p t,表示在节点 $ p $ 上发送一条大小为 $ t $ 的信息。

输出格式

输出一行,包含 $ n $ 个整数,相邻整数之间用一个空格分割,依次表示进行完上述操作后节点 $ 1 $ 至节点 $ n $ 上存储信息的大小。

数据范围

$ 1 \le n \le 10000 \(, \) 1 \le m \le 10^5 \(, \) 1 \le t \le 100 $

输入样例1:

4 8
1 1 2
2 1 10
2 3 5
1 4 1
2 2 2
1 1 2
1 2 4
2 2 1

输出样例1:

13 13 5 3

并查集高级操作

这道题乍一看和食物链挺像,但我感觉区别还是比较明显的。食物链维护的是当前节点到父节点和根节点的距离,是维护边。

但这个题因为是对于某个节点,只要一发消息,所连通的节点都要收到消息,所以维护的是节点,不是边了。

/*
修改操作:改的时候不是暴力给所有点加,而是给一棵树的根节点作为代表元素加
那么每个节点最终真实的权值=该点到根节点的路径权值之和。
经过路径压缩后,每个节点的最终答案是当前点的权值+根节点的权值

加边的方式:
1、建立一个空的权值为0的节点作为两棵树的新的根节点
2、在一棵树的根节点指向另一棵树的根节点之前,提前把一个树的根节点减去另一个根节点的权值

路径压缩:1、类似食物链那道题。先判断当前节点是否为根节点或者当前节点的父节点是否为根节点。
2、如果是普通的情况,那么我们就需要先更新父节点的情况,即先让父节点指向根节点
因为路径压缩是递归处理,执行晚int root = find(p[x]) 之后,父节点的权值已经更新完毕
而且父节点及其以上的节点都指向了根节点,那么现在就更新当前节点x,先更新权值
即d[x]=d[x]+d[p[x]],同时让当前节点x指向根节点root。完毕~

*/

#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int n,m;
int p[N],d[N];

int find(int x)
{
    if (p[x]==x||p[p[x]]==p[x])
        return p[x];
    
    int root = find(p[x]); // 这句执行完,zx的父节点及其以上的节点就完成了压缩过程
    d[x]+=d[p[x]];
    p[x]=root;
    return p[x];
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) p[i]=i;
    
    while (m--)
    {
        int op;
        scanf("%d",&op);
        if (op==1)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            int pa = find(a);
            int pb = find(b);
            if (pa!=pb)
            {
                d[pa]-=d[pb];
                p[pa]=pb;
            }
        }
        else
        {
            int a,t;
            scanf("%d%d",&a,&t);
            int pa = find(a);
            d[pa]+=t;
        }
    }
    
    for (int i=1;i<=n;i++)
        if (i==find(i)) printf("%d ",d[i]);
        else printf("%d  ",d[i]+d[find(i)]);
    puts("");
    
    return 0;
    
    
    
}

超级胶水

迷惑性极强的问题,石子合并 很像。但仔细分析之后,发现是找规律问题,这也是给我一个警醒,要认真分析,多做数据模拟!!!

#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n;
typedef long long ll;
int main()
{
    ll ans = 0;
    ll sum = 0;
    scanf("%d",&n);
    for (int i=0;i<n;i++)
    {
        int x;
        scanf("%d",&x);
        ans+=sum*x;
        sum+=x;
    }
    
    printf("%lld\n",ans);
    return 0;
}