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

真题

1.时间显示

题目描述

小蓝要和朋友合作开发一个时间显示的网站。

在服务器上,朋友已经获取了当前的时间,用一个整数表示,值为从 1970 年 1 月 1 日 00:00:00 到当前时刻经过的毫秒数。

现在,小蓝要在客户端显示出这个时间。

小蓝不用显示出年月日,只需要显示出时分秒即可,毫秒也不用显示,直接舍去即可。

给定一个用整数表示的时间,请将这个时间对应的时分秒输出。

输入格式
输入一行包含一个整数,表示时间。

输出格式
输出时分秒表示的当前时间,格式形如 HH:MM:SS,其中 HH 表示时,值为 0 到 23,MM 表示分,值为 0 到 59,SS 表示秒,值为 0 到 59。

时、分、秒不足两位时补前导 0。

数据范围
对于所有评测用例,给定的时间为不超过 1018 的正整数。

输入样例1:

46800999

输出样例1:

13:00:00

输入样例2:

1618708103123

输出样例2:

01:08:23

解题思路

1秒=1000毫秒,1分钟=60秒,1小时=60分钟,所以1毫秒=1/10001/60000分钟=1/3600000小时,直接换算即可,但要求h取值(0\~23),m取值(0\~59),s取值(0~59),用取余即可

代码实现

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long LL;
LL n;
int main()
{
    cin>>n;
    LL h=n/1000/3600;//得到小时 
    LL m=n/1000/60;//得到分钟
    LL s=n/1000;//得到秒
    printf("%02lld:%02lld:%02lld",h%24,m%60,s%60);
}

2.砝码称重

题目描述

你有一架天平和 N 个砝码,这 N 个砝码重量依次是 W1,W2,⋅⋅⋅,WN。

请你计算一共可以称出多少种不同的正整数重量?

注意砝码可以放在天平两边。

输入格式
输入的第一行包含一个整数 N。

第二行包含 N 个整数:W1,W2,W3,⋅⋅⋅,WN。

输出格式
输出一个整数代表答案。

数据范围
对于 50% 的评测用例,1≤N≤15。
对于所有评测用例,1≤N≤100,N 个砝码总重不超过 105。

输入样例:

3
1 4 6

输出样例:

10

样例解释
能称出的 10 种重量是:1、2、3、4、5、6、7、9、10、11。

1 = 1;
2 = 6 − 4 (天平一边放 6,另一边放 4);
3 = 4 − 1;
4 = 4;
5 = 6 − 1;
6 = 6;
7 = 1 + 6;
9 = 4 + 6 − 1;
10 = 4 + 6;
11 = 1 + 4 + 6。

解题思路

题型:背包,dp
闫氏dp
1.状态表示f[i][j]
集合:表示从前i个砝码选,总质量为j的方案数
属性:是否为空
2.状态计算
f[i][j]=f[i-1][j]||f[i-1][j+w[i]]||f[i-1][j-abs(w[i])
解释一下:对于每一个砝码,都有三种选择,选或者不选,对于第i个物品,如果我们不选,那么根据集合的表示可知,从前i个物品选总质量为j,所以不选的时候要保证质量为j,则就是f[i-1][j],就是从前i-1个砝码选,总质量为j,同理,如果减去i个砝码的总质量为j,那么前i-1个砝码选择的质量就是j+w[i],即f[i-1][j+w[i]],如果加上i个砝码的质量的总质量为j,那么从前i-1个砝码中选的总质量就为j-w[i],即f[i-1][j-w[i]],由实际含义可以,f[i][j]=f[i][-j]因为可以通过镜像的操作而得到,故而f[i][j-w]=f[i][w-j]=f[i][abs(j-w)]。就避免了数组下标为负数的情况。而这三种情况只要有一种能表示质量j,就说明能称出质量为j的,那么就有不为空,所以三种情况用或运算即可。在这道题,最大能表示的重量就是所有给的砝码的重量之和,最小就是0,所以f[0][0]初始化为true。所以最后的答案就是f[n][i],其中i0到最大重量,即从前n件物品中选,总体积为i的方案数。

代码实现

#include<iostream>
#include<cstdio>
using namespace std;
const int N=110,M=200010;
int w[N];
bool f[N][M];
int n,sum;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]),sum+=w[i];
    f[0][0]=1;
    for(int i=1;i<=n;i++){
        for(int j=0;j<=sum;j++){
            f[i][j]=f[i-1][j]||f[i-1][j+w[i]]||f[i-1][abs(j-w[i])];
        }
    }
    int res=0;
    for(int i=1;i<=sum;i++){
        if(f[n][i]) res++;
    }
    printf("%d\n",res);
    return 0;
}

3.杨辉三角

题目描述

下面的图形是著名的杨辉三角形:

在这里插入图片描述
如果我们按从上到下、从左到右的顺序把所有数排成一列,可以得到如下数列:

1, 1, 1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1, …

给定一个正整数 N,请你输出数列中第一次出现 N 是在第几个数?

输入格式
输入一个整数 N。

输出格式
输出一个整数代表答案。

数据范围
对于 20% 的评测用例,1≤N≤10;
对于所有评测用例,1≤N≤109。

输入样例:

6

输出样例:

13

解题思路

杨辉三角的特点:左右对称,而且中间的数最大,如果找第一次出现的n,一定在左边,所以只看左半边就行
在这里插入图片描述
通过这个图形可以看出,斜着看每一行,越往下越大,横着看每一行,越往右越大,即中间的数会比两边大,中间的数满足规律,也就是C(2n,n),而且斜着和横着都是单调递增的,所以可以想到用二分
k枚举斜行,从哪里开始枚举?我们要求的是等于k的最小的数,所以我们应该从最大的开始枚举,也就是C(2k,k),因为**假设最右边的数就是要找到n,它一定是最小的,因为它左边的数都比它小,不满足等于n,而它右边和下边的数,就算等于n,也不会比它先出现。**所以二分的时候,我们就枚举k。
二分的左右边界上限,一定是2*k,因为它是最大的,下限,一定是n,因为这个题一定有解,即C(n,1)=n
k的取值,本题k去16,因为我们从C(2k,k)开始枚举,N的最大值是10^9,而C(32,16)<10\^9C(34,17)>10^9
计算求出来的C(r,k)是第几个数,第0行一个数,第一行1个数,第二行3个…第rr+1,等差数列求和r*(r+1)/2,第k斜行是第k+1个数,所以该位置是r*(r+1)/2+k+1

代码实现

#include<iostream>
#include<algorithm>
#include<cstdio>

using namespace std;

typedef long long LL;
int n;
LL C(int a,int b){//求组合数
    LL res=1;
    for(int i=a,j=1;j<=b;j++,i--){
        res=res*i/j;
        if(res>n) return res;//大于n就没必要再求了 防止爆LL
    }
    return res;
}

bool check(int k){//检查C(,k),k表示斜行,我们枚举的是斜行
    int l=2*k,r=max(n,l);//因为最大的数是最靠近中间的,也就是C(2*k,k),所以上阶是2*k,该题一定有解,因为C(n,1)=n,所以下界是n
    while(l<r){
        int mid=l+r>>1;
        if(C(mid,k)>=n) r=mid;//因为枚举斜行,行数越小,组合数越小,当我们枚举的数太大,就说明在上面的斜行
        else l=mid+1;
    }
    if(C(r,k)!=n) return false;
    cout<<1LL*r*(r+1)/2+k+1<<endl;//第0行一个数,第一行1个数,第二行3个..第r行r+1,等差数列求和r*(r+1)/2,第k斜行是第k+1个数,所以该位置是r*(r+1)/2+k+1
    return true;
}

int main()
{
    cin>>n;
    for(int k=16;;k--)//从斜行最大的16开始枚举
        if(check(k)) break;
    return 0;
}

4.双向排序

题目描述

给定序列 (a1,a2,⋅⋅⋅,an)=(1,2,⋅⋅⋅,n),即 ai=i。

小蓝将对这个序列进行 m 次操作,每次可能是将 a1,a2,⋅⋅⋅,aqi 降序排列,或者将 aqi,aqi+1,⋅⋅⋅,an 升序排列。

请求出操作完成后的序列。

输入格式
输入的第一行包含两个整数 n,m,分别表示序列的长度和操作次数。

接下来 m 行描述对序列的操作,其中第 i 行包含两个整数 pi,qi 表示操作类型和参数。当 pi=0 时,表示将 a1,a2,⋅⋅⋅,aqi 降序排列;当 pi=1 时,表示将 aqi,aqi+1,⋅⋅⋅,an 升序排列。

输出格式
输出一行,包含 n 个整数,相邻的整数之间使用一个空格分隔,表示操作完成后的序列。

数据范围
对于 30% 的评测用例,n,m≤1000;
对于 60% 的评测用例,n,m≤5000;
对于所有评测用例,1≤n,m≤105,0≤pi≤1,1≤qi≤n。

输入样例:

3 3
0 3
1 2
0 2

输出样例:

3 1 2

样例解释
原数列为 (1,2,3)。

第 1 步后为 (3,2,1)。

第 2 步后为 (3,1,2)。

第 3 步后为 (3,1,2)。与第 2 步操作后相同,因为前两个数已经是降序了。

解题思路

找规律
第一种情况:
在这里插入图片描述
第二种情况:
在这里插入图片描述
第三种情况:
在这里插入图片描述
第四种情况:
在这里插入图片描述
所以,我们只需要看是连续前缀还是连续后缀,又或者是前缀混合后缀且第三段比前两段短,又或者是前缀混合后缀且第三段比前两段长,但每一次都只需要考虑端点,根据情况取最大(前缀)或者最小(后缀)端点。

代码实现

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
#define x first 
#define y second 
typedef pair<int,int> PII;
const int N=100010;
int n,m;
PII stk[N];//第一个参数为0或者1表示进行前缀操作还是后缀操作
int res[N];

int main()
{
    scanf("%d%d",&n,&m);
    int top=0;
    while(m--){
        int p,q;
        scanf("%d%d",&p,&q);
        if(!p){//进行前缀操作
            while(top&&stk[top].x==0) q=max(q,stk[top--].y);//如果是连续的前缀操作,只需要保留最长的前缀
            while(top>=2&&stk[top-1].y<=q) top-=2;//如果有一段前缀一段后缀连续,且比当前的前缀操作短,可直接删去两段
            stk[++top]={0,q};
        }
        else if(top){//当栈不空再进行后缀,因为后缀是正序,如果第一个操作是后缀操作没有意义
            while(top&&stk[top].x==1) q=min(q,stk[top--].y);
            while(top>=2&&stk[top-1].y>=q) top-=2;
            stk[++top]={1,q};
        }
    }
    int k=n,l=1,r=n;//k用来表示要填的数1~n,l,r是枚举的左右端点,根据栈里的操作填数
    for(int i=1;i<=top;i++){
        
        if(stk[i].x==0)
            while(stk[i].y<r&&l<=r) res[r--]=k--;//如果是前缀,就是将前半段逆序,但是后半段不变,可直接填数
        else
            while(stk[i].y>l&&l<=r) res[l++]=k--;//如果是后缀,就是将后半段正序,但是前半段不变,可直接填数
    
        if(l>r) break;
    }
    if(top%2)// 若l < r, 表示中间还有些数没有填上,操作次数为奇数,则下一次操作为前缀操作
        while(l<=r) res[l++]=k--;
    else 
        while(l<=r) res[r--]=k--;
    for(int i=1;i<=n;i++) printf("%d ",res[i]);
    return 0;
}

5.括号序列

题目描述

给定一个括号序列,要求尽可能少地添加若干括号使得括号序列变得合法,当添加完成后,会产生不同的添加结果,请问有多少种本质不同的添加结果。

两个结果是本质不同的是指存在某个位置一个结果是左括号,而另一个是右括号。

例如,对于括号序列 (((),只需要添加两个括号就能让其合法,有以下几种不同的添加结果:()()()、()(())、(())()、(()()) 和 ((()))。

输入格式
输入一行包含一个字符串 s,表示给定的括号序列,序列中只有左括号和右括号。

输出格式
输出一个整数表示答案,答案可能很大,请输出答案除以 1000000007 (即 109+7) 的余数。

数据范围
对于 40% 的评测用例,|s|≤200。
对于所有评测用例,1≤|s|≤5000。

输入样例:

((()

输出样例:

5

解题思路

左括号的添加和右括号的添加是相互独立的,可以分开来算,证明如下
在这里插入图片描述
所以先计算左括号有多少种添加方案,再计算右括号有多少种添加方案,两个结果相乘就行
如何保证计算左括号的添加方案时不会重复,可以以所有右括号分割,将每一个部分看成一段,然后我们计算时规定,只在右括号前面添加左括号,所以枚举的时候,遇到左括号前面不用添加任何东西,遇到右括号前面可以添加任意多个左括号
在这里插入图片描述
然后可以解题了,用闫氏dp
状态表示f[i][j]
集合:表示只考虑前i个括号,左括号比右括号多j个元素的集合
属性:数量
状态计算:1. 如果当前字符是左括号,不用添加f[i][j]=f[i-1][j-1]
2. 如果当前字符是右括号,f[i][j]=f[i-1][j+1]+f[i][j-1]
对于状态计算1来说,如果当前字符是左括号,不用添加,所以根据集合描述可知,考虑前i个括号,左括号比右括号多j个,那么考虑前i-1个,减去第i个左括号,就多1个左括号,所以f[i][j]=f[i-1][j-1]
对于状态计算2,如果当前字符是右括号,我们可以添加0个(不添加,f[i-][j+1],因为考虑前i个,左比右多j,那么去掉第i个右,前i-1,左比右多j+1个),添加1个f[i-1][j]…添加j+1个左括号(f[i-1][0],因为前i个括号,左比右多j,除掉添加的j+1个,原来左比右多0个)f[i][j]=f[i-1][j+1]+f[i-1][j]+...+f[i-1][0],经过下面的换算:
在这里插入图片描述
红色部分替换,最后得到f[i][j]=f[i-1][j+1]+f[i][j-1]
最后的结果是遍历f[n][j]j0n,找到第一个值不为零的数就是答案,因为题目中说要求尽可能少地添加若干括号使得括号序列变得合法,所以当j越小,添加的括号越小。

代码实现

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;

const int N=5010,mod=1e9+7;
char str[N];
LL f[N][N];
int n;
int cal()
{
    memset(f,0,sizeof(f));
    f[0][0]=1;
    for(int i=1;i<=n;i++){
        if(str[i]=='('){//如果是左括号,无须添加
            for(int j=1;j<=n;j++)
                f[i][j]=f[i-1][j-1];
        }else{//如果是右括号
            f[i][0]=(f[i-1][0]+f[i-1][1])%mod;//为了防止j-1得到-1越界,特别处理一下
            for(int j=1;j<=n;j++){
                f[i][j]=(f[i-1][j+1]+f[i][j-1])%mod;
            }
        }
    }
    for(int i=0;i<=n;i++)
        if(f[n][i]) return f[n][i];
    return -1;
}
int main(){
    scanf("%s",str+1);
    n=strlen(str+1);
    LL l=cal();
    
    reverse(str+1,str+n+1);
    for(int i=1;i<=n;i++){
        if(str[i]=='(') str[i]=')';
        else str[i]='(';
    }
    
    LL r=cal();
    printf("%lld",l*r%mod);
    return 0;
}

6.异或数列

题目描述

Alice 和 Bob 正在玩一个异或数列的游戏。

初始时,Alice 和 Bob 分别有一个整数 a 和 b(初始时,a,b 均为 0),有一个给定的长度为 n 的公共数列 X1,X2,⋅⋅⋅,Xn。

Alice 和 Bob 轮流操作,Alice 先手,每步可以在以下两种选项中选一种:

选项 1:从数列中选一个 Xi 给 Alice 的数异或上,或者说令 a 变为 a⊕Xi。(其中 ⊕ 表示按位异或)
选项 2:从数列中选一个 Xi 给 Bob 的数异或上,或者说令 b 变为 b⊕Xi。
每个数 Xi 都只能用一次,当所有 Xi 均被使用后(n 轮后)游戏结束。

游戏结束时,拥有的数比较大的一方获胜,如果双方数值相同,即为平手。

现在双方都足够聪明,都采用最优策略,请问谁能获胜?

输入格式
每个评测用例包含多组询问。询问之间彼此独立。

输入的第一行包含一个整数 T,表示询问数。

接下来 T 行每行包含一组询问。其中第 i 行的第一个整数 ni 表示数列长度,随后 ni 个整数 X1,X2,⋅⋅⋅,Xni 表示数列中的每个数。

输出格式
输出 T 行,依次对应每组询问的答案。

每行包含一个整数 1、0 或 −1 分别表示 Alice 胜、平局或败。

数据范围
对于所有评测用例,1≤T≤2×105,1≤∑i=1Tni≤2×105,0≤Xi<220。

输入样例:

4
1 1
1 0
2 2 1
7 992438 1006399 781139 985280 4729 872779 563580

输出样例:

1
0
1
1

解题思路

初始时A和B都为0, 由异或的性质最终A⨁B=X1⨁X2⨁Xi...
如果所有X异或的结果为0,那么说明最终的A 和 B 是相同的,直接输出平局0。
由于异或中: 与 1 异或是取反,与 0 异或不变,
当某一位的num[i]为偶数,则游戏结果的这一位一定相等,直接看下一位出现的次数
如果num[i]的个数为1,则一定是先手赢(抢了1之后,就没有1了|只有A最高位变成了1)
那么当num[i]为大于1的奇数, 0的个数为偶数, 则先手赢
当num[i]为大于1的奇数, 0的个数为奇数,则后手赢

代码实现

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=22;
int num[N];
int get_num(int x){
    int cnt=1;
    while(x){
        if(x&1) num[cnt]++;
        cnt++;
        x>>=1;
    }
}
int main()
{
    int n;
    cin>>n;
    while(n--){
        memset(num,0,sizeof num);
        int x,t,sum=0;
        scanf("%d",&t);
        for(int i=1;i<=t;i++){
            scanf("%d",&x);
            get_num(x);
            sum^=x;
        }
        if(!sum) puts("0");
        else{
            for(int i=20;i>=1;i--){
                if(num[i]==1){
                    puts("1");
                    break;
                }else if(num[i]&1){
                        if(t%2==0) puts("-1");
                        else puts("1");
                        break;
                    }
                }
            }
        }
}

7.左孩子右兄弟

题目描述

对于一棵多叉树,我们可以通过 “左孩子右兄弟” 表示法,将其转化成一棵二叉树。

如果我们认为每个结点的子结点是无序的,那么得到的二叉树可能不唯一。

换句话说,每个结点可以选任意子结点作为左孩子,并按任意顺序连接右兄弟。

给定一棵包含 N 个结点的多叉树,结点从 1 至 N 编号,其中 1 号结点是根,每个结点的父结点的编号比自己的编号小。

请你计算其通过 “左孩子右兄弟” 表示法转化成的二叉树,高度最高是多少。

注:只有根结点这一个结点的树高度为 0。

例如如下的多叉树:
在这里插入图片描述
可能有以下 3 种 (这里只列出 3 种,并不是全部) 不同的 “左孩子右兄弟”表示:
在这里插入图片描述
其中最后一种高度最高,为 4。

输入格式
输入的第一行包含一个整数 N。

以下 N−1 行,每行包含一个整数,依次表示 2 至 N 号结点的父结点编号。

输出格式
输出一个整数表示答案。

数据范围
对于 30% 的评测用例,1≤N≤20;
对于所有评测用例,1≤N≤105。

输入样例:

5
1
1
1
2

输出样例:

4

解题思路

二叉树转化成树:连接所有兄弟节点,只保留最右边的那条与父母的连线,其他兄弟和父母的连线删除,每次这样做之后,如果想要得到树的最大高度,那么应该保留一个孩子成为右节点,其他结点成为保留的兄弟的孩子,那么最大高度就是根节点孩子的数目加上,以孩子结点为根的子树的最大高度,所以如果用f[u]表示以u为根的子树的最大高度,那么f[u]=max(f[u],f[j]+num[u]),其中j为u的孩子,num[u]表示以u为根的孩子的数目

代码实现

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100010;
int h[N],e[N],ne[N],idx;
int n;
int num[N],f[N];
void add(int a,int b){//邻接表
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u){//递归搜索
    
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        dfs(j);//先求所有的孩子树的高度  再回溯求根的
        f[u]=max(f[u],f[j]+num[u]);//以u为根节点的树的最大高度等于,以u为根的孩子的个数加上某个子树的最大高度
    }
}
int main()
{
    cin>>n;
    memset(h,-1,sizeof h);
    for(int i=2;i<=n;i++){
        int x;
        scanf("%d",&x);
        add(x,i);
        num[x]++;
    }
    dfs(1);
    printf("%d\n",f[1]);
    return 0;
}

8.最少砝码

题目描述

你有一架天平。现在你要设计一套砝码,使得利用这些砝码可以称出任意小于等于 N 的正整数重量。

那么这套砝码最少需要包含多少个砝码?

注意砝码可以放在天平两边。

输入格式
输入包含一个正整数 N。

输出格式
输出一个整数代表答案。

数据范围
对于所有评测用例,1≤N≤109。

输入样例:

7

输出样例:

3

样例解释
3 个砝码重量是 1、4、6,可以称出 1 至 7 的所有重量。

1 = 1;
2 = 6 − 4 (天平一边放 6,另一边放 4);
3 = 4 − 1;
4 = 4;
5 = 6 − 1;
6 = 6;
7 = 1 + 6;
少于 3 个砝码不可能称出 1 至 7 的所有重量。

解题思路

大家可以参考这篇博客

代码实现

#include<iostream>
#include<cstdio>
using namespace std;
int n;
int main()
{
    cin>>n;
    
    int cnt=1,weight=1,sum=1;
    while(1){
        if(sum>=n) break;
        weight*=3;
        sum+=weight;
        cnt++;
    }
    printf("%d\n",cnt);
    return 0;
}
  • 28
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值