2022年第十三届蓝桥杯省赛C++B组

A.九进制转十进制

【问题描述】
九进制正整数(2022)₉转换成十进制等于多少?

【思路】

2 * 9^3 + 0 * 9^2 + 2 * 9^1 + 2 * 9^0=1478

#include<iostream>

using namespace std;

int main()
{
	printf("1478\n");
	
	return 0;
}

签到题没什么好说的吧

B.顺子日期


问题描述

小明特别喜欢顺子。顺子指的就是连续的三个数字:123、456等。顺子日期指的就是在日期的yyyymmdd表示法中,存在任意连续的三位数是一个顺子的日期。例如20220123就是一个顺子日期,因为它出现了一个顺子:123;而20221023则不是一个顺子日期,它一个顺子也没有。小明想知道在整个2022年份中,一共有多少个顺子日期。

思路

要么mmd为顺子,要么mdd为顺子,无论怎么样第二个m和第一个d总满足m+1=d;

#include<iostream>

using namespace std;

int month[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
int cnt;

int main()
{
	for(int m1=0;m1<2;m1++)
	{
		for(int m2=0;m2<10;m2++)
		{
			int d1=m2+1;
			int m=m1*10+m2;
			
			if(m<0||m>12) continue;
			
			for(int d2=0;d2<10;d2++)
			{
				int d=d1*10+d2;
				if(d>0&&d<=month[m]&&(m1+1==m2||d1+1==d2)) cnt++;
			}
		}
	}
	printf("%d\n",cnt);
	
	return 0;
}

试题 C: 刷题统计
 

【问题描述】 小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天 做 a 道题目,周六和周日每天做 b 道题目。请你帮小明计算,按照计划他将在 第几天实现做题数大于等于 n 题?

【输入格式】 输入一行包含三个整数 a, b 和 n.

【输出格式】 输出一个整数代表天数。

【样例输入】 10 20 99

【样例输出】 8

【评测用例规模与约定】

对于 50% 的评测用例,1 ≤ a, b, n ≤ 106 .

对于 100% 的评测用例,1 ≤ a, b, n ≤ 1018 .

【思路】

一个星期可以刷的题目数,然后拿题数整除一星期可以刷的题目,然后算剩下的题目需要几天完成

#include<iostream>

using namespace std;

long long a,b,n;

int main()
{
	scanf("%lld%lld%lld",&a,&b,&n);
	
	long long s,m;
	int d=0;
	
	s=a*5+b*2;  
	
	m=n/s*7;
	
	n=n%s;
	
	if (n <= a * 5) {
		m += n / a + (n % a != 0);
	}
	else {
		n -= a * 5;
		m += 6 + (n > b);
	}

	printf("%lld\n",m);
	
	return 0;
}

D.修剪灌木

【问题描述】 爱丽丝要完成一项修剪灌木的工作。 有 N 棵灌木整齐的从左到右排成一排。爱丽丝在每天傍晚会修剪一棵灌 木,让灌木的高度变为 0 厘米。爱丽丝修剪灌木的顺序是从最左侧的灌木开始, 每天向右修剪一棵灌木。当修剪了最右侧的灌木后,她会调转方向,下一天开 始向左修剪灌木。直到修剪了最左的灌木后再次调转方向。然后如此循环往复。 灌木每天从早上到傍晚会长高 1 厘米,而其余时间不会长高。在第一天的 早晨,所有灌木的高度都是 0 厘米。爱丽丝想知道每棵灌木最高长到多高。

【输入格式】 一个正整数 N ,含义如题面所述。

【输出格式】 输出 N 行,每行一个整数,第行表示从左到右第 i 棵树最高能长到多高。

【样例输入】 3

【样例输出】 4 2 4

【评测用例规模与约定】 对于 30% 的数据,N ≤ 10. 对于 100% 的数据,1 < N ≤ 10000.

【思路】

长到最高的时间段有两种可能:被剪后往右剪再拐回来,和被剪之后往左剪再拐回来,假设某个灌木左侧有 x 棵灌木,右侧有 y 棵,容易发现这颗灌木的最大高度是 max(x, y) * 2,它的左(右)侧每有一颗灌木被剪前它都会长高 1 厘米,包括它自己被剪之前。

#include<iostream>

using namespace std;

int n;

int main()
{
	scanf("%d",&n);
	
	for(int i=0;i<n;i++)
	{
		int x=max(i,n-i-1);
		
		printf("%d\n",x*2);	
	}
 }

E.X进制减法

【问题描述】 进制规定了数字在数位上逢几进一。 X 进制是一种很神奇的进制,因为其每一数位的进制并不固定!例如说某 种 X 进制数,最低数位为二进制,第二数位为十进制,第三数位为八进制,则 X 进制数 321 转换为十进制数为 65。 现在有两个 X 进制表示的整数 A 和 B,但是其具体每一数位的进制还不确 定,只知道 A 和 B 是同一进制规则,且每一数位最高为 N 进制,最低为二进 制。请你算出 A − B 的结果最小可能是多少。 请注意,你需要保证 A 和 B 在 X 进制下都是合法的,即每一数位上的数 字要小于其进制。

【输入格式】

第一行一个正整数 N,含义如题面所述。

第二行一个正整数 Ma,表示 X 进制数 A 的位数。

第三行 Ma 个用空格分开的整数,表示 X 进制数 A 按从高位到低位顺序各 个数位上的数字在十进制下的表示。

第四行一个正整数 Mb,表示 X 进制数 B 的位数。

第五行 Mb 个用空格分开的整数,表示 X 进制数 B 按从高位到低位顺序各 个数位上的数字在十进制下的表示。 请注意,输入中的所有数字都是十进制的。

【输出格式】 输出一行一个整数,表示 X 进制数 A − B 的结果的最小可能值转换为十进 制后再模 1000000007 的结果。

【样例输入】

11

3

10 4 0

3

1 2 0

【样例输出】 94

【样例说明】 当进制为:最低位 2 进制,第二数位 5 进制,第三数位 11 进制时,减法 得到的差最小。此时 A 在十进制下是 108,B 在十进制下是 14,差值是 94。

【评测用例规模与约定】 对于 30% 的数据,N ≤ 10; Ma, Mb ≤ 8. 对于 100% 的数据,2 ≤ N ≤ 1000; 1 ≤ Ma, Mb ≤ 100000; A ≥ B

【思路】 推出进制计算方式:每一位数字,乘以该数字数位后所有进制数,求和即为结果
欲使A-B最小,只需使得各位数字取得合法范围内的最小进制即可,具体做法就是对A和B中相同数位的数字取xmax = max(a[i], b[i]),该位的合法最小进制即为max(xmax + 1, 2)
因为最小进制不能小于2;而对于X进制的数来说,合法的最大数字是X-1,例如8进制中最大数字是7,二进制中最大数字是1。
而求A和B的值,只需要再开一个数组存储各位数字的实际权重,再将各位数字和对应的权重相乘后相加即可。
需要注意的是这个题数据比较大,需要多次取模,特别是最后计算最终结果的时候,应(A - B + mod) % mod,否则可能A本来比B大,但是取模后比B小,这样A-B可能会出现负值。
 

#include <iostream>

using namespace std;

const int N=1e5+10,mm=1000000007;
long long a[N],b[N],w[N],mul[N]; 
long long A,B;
//a存储A各位数字,b存储B各位数字,w存储各位的进制,mul存储各位数字的实际权重
int main(){
    int n,ma,mb;
    cin>>n;  // 最大进制
    cin>>ma;
    for(int i=ma;i>=1;i--) cin>>a[i];
    cin>>mb;
    for(int i=mb;i>=1;i--) cin>>b[i];
    // 确定各位进制 w[i]
    int maxx=max(ma,mb); // A B 中最大的位数
    for(int i=maxx;i>=1;i--){
        int tmax=max(a[i],b[i]);
        w[i]=max(2,tmax + 1);
    }
    // 计算权重
    mul[1]=1;
    for(int i=2;i<=maxx;i++){
        mul[i]=w[i-1]*mul[i-1]%mm;
    }
    // 计算A
    for(int i=ma;i>=1;i--){
        A=(A+a[i]*mul[i])%mm;
    }
    // 计算B
    for(int i=mb;i>=1;i--){
        B=(B+b[i]*mul[i])%mm;
    }
    // A - B
    cout<<(A-B+mm)%mm<<endl;
    return 0;
}

试题 F: 统计子矩阵 :
 

【问题描述】 给定一个 N × M 的矩阵 A,请你统计有多少个子矩阵 (最小 1 × 1,最大 N × M) 满足子矩阵中所有数的和不超过给定的整数 K?

【输入格式】 第一行包含三个整数 N, M 和 K. 之后 N 行每行包含 M 个整数,代表矩阵 A.

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

【样例输入】

3 4 10

1 2 3 4

5 6 7 8

9 10 11 12

【样例输出】 19

【评测用例规模与约定】

对于 30% 的数据,N, M ≤ 20. 对于 70% 的数据,N, M ≤ 100. 对于 100% 的数据,1 ≤ N, M ≤ 500; 0 ≤ Ai j ≤ 1000; 1 ≤ K ≤ 250000000.

【思路】i,j分别表示一个区域的左右边界,p,q来表示上下两个指针,p一开始在最上面,不断向下来找q,至到权值和小于等于k(而且不能越界也就是p>q),然后p q这片区域的行数q-p+1就是子矩阵的个数;

#include <bits/stdc++.h>

using namespace std;

int n, m, k;
int a[505][505];
long long ans = 0;

int main() {
    //ios::sync_with_stdio(false);
    //cin >> n >> m >> k;
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            scanf("%d", &a[i][j]);
            //二维前缀和,第i行j列格子左上部分所有元素的和
            a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
        }
    }
    for (int i = 1; i <= m; ++i) {
        for (int j = i; j <= m; ++j) {
            for (int p = 1, q = 1; q <= n; ++q) {
                while (p <= q && a[q][j] - a[q][i - 1] - a[p - 1][j] + a[p - 1][i - 1] > k) {
                    //一直让p指针下移到权值和<=k
                    p++;
                }
                if (p <= q) {
                    //在当前i,j的情况下,此时p q 之间的所有子矩阵都满足条件,一共q-p+1行
                    ans += (q - p + 1);
                }
            }
        }
    }
    cout << ans;
    return 0;
}


试题 G: 积木画


【问题描述】 小明最近迷上了积木画,有这么两种类型的积木,分别为 I 型(大小为 2 个单位面积)和 L 型(大小为 3 个单位面积):

同时,小明有一块面积大小为 2 × N 的画布,画布由 2 × N 个 1 × 1 区域构成。小明需要用以上两种积木将画布拼满,他想知道总共有多少种不同的方式? 积木可以任意旋转,且画布的方向固定。

【输入格式】 输入一个整数 N,表示画布大小。

【输出格式】 输出一个整数表示答案。由于答案可能很大,所以输出其对 1000000007 取 模后的值

【样例输入】 3

【样例输出】 5

【样例说明】 五种情况如下图所示,颜色只是为了标识不同的积木:

【评测用例规模与约定】 对于所有测试用例,1 ≤ N ≤ 10000000.

【思路】 用 f[i] 表示i列的方法总数

接下来把状态分一下类

1. 第 i-1列已经排满的基础上,如图,方法数等于f[i-1]

 2.若第 i-1列没排任何积木(即与第i列构成如下图),方法数等于f[i-2]

 3.

①若第 i-1列排了一半,只有如下两种情况, 方法数等于2*f[i-3]

 ②上面的情况并不全面, 我发现第i-3置身事外

那用不用考虑下面这种情况呢, 明显是不用的,与上面情况2重复,

 新的情况已经出现!, 方法数等于2*f[i-3]

③这次把第i-4列拉下水, 这也是我们未曾考虑到的情况, 2*f[i-4]

综上, 大胆假设出:


原文链接:https://blog.csdn.net/qq_39391544/article/details/124060739

#include<iostream>
using namespace std;
 
const int N = 1e7+10, MOD = 1000000007;
int f[N];
int main()
{
    f[1] = 1;
    f[2] = 2;
    f[3] = 5;
    int n;
    cin >> n;
    for(int i = 4; i <= n; i ++)
    {
        f[i] = (2*f[i-1]%MOD+f[i-3]%MOD)%MOD;
    }
    cout << f[n];
    return 0;
}

【问题描述】小明最近迷上了一款名为《扫雷》的游戏。其中有一个关卡的任务如下:在一个二维平面上放置着 n个炸雷,第 i 个炸雷 (xi,yi,ri) 表示在坐标 (xi,yi) 处存在一个炸雷,它的爆炸范围是以半径为 ri的一个圆。为了顺利通过这片土地,需要玩家进行排雷。玩家可以发射 m

个排雷火箭,小明已经规划好了每个排雷火箭的发射方向,第 j 个排雷火箭 (xj,yj,rj) 表示这个排雷火箭将会在 (xj,yj) 处爆炸,它的爆炸范围是以半径为 rj的一个圆,在其爆炸范围内的炸雷会被引爆。同时,当炸雷被引爆时,在其爆炸范围内的炸雷也会被引爆。现在小明想知道他这次共引爆了几颗炸雷?你可以把炸雷和排雷火箭都视为平面上的一个点。一个点处可以存在多个炸雷和排雷火箭。当炸雷位于爆炸范围的边界上时也会被引爆。

【输入格式】

输入的第一行包含两个整数 n、m。接下来的 n行,每行三个整数 xi,yi,ri,表示一个炸雷的信息。再接下来的 m行,每行三个整数 xj,yj,rj,表示一个排雷火箭的信息。

【输出格式】

输出一个整数表示答案。

数据范围对于 40%的评测用例:0≤x,y≤109,0≤n,m≤103,1≤r≤10,对于 100% 的评测用例:0≤x,y≤109,0≤n,m≤5×104,1≤r≤10。

/**
    图的深度优先遍历,邻接表实现
    由于点数有1e5,那么遍历所有图上的点是否联通,需要O(n^2)也就是需要2.5e9,
    明显会超时
*/
/**
#include <iostream>
#include <cstdio>
#include <cmath>
#include <vector>
using namespace std;
 
const int maxn=1010;
struct Node
{
    int x,y,r;
}cir[maxn];
vector<int> adj[maxn];
bool vis[maxn]={false};
int n,m;
int dfs_Trave(int x,int y,int r);
int dfs(int index);
void add(int index);
int main()
{
    scanf("%d%d",&n,&m);
    int x,y,r;
    for(int i=0;i<n;++i)
    {
        scanf("%d%d%d",&x,&y,&r);
        cir[i]={x,y,r};
    }
 
    for(int i=0;i<n;++i)
    {
        add(i);
    }
//    cout << "调试:\n";
//    for(int i=0;i<n;++i)
//    {
//        for(auto b:adj[i])
//            cout << b << ' ';
//        cout << endl;
//    }
//    cout << "jk\n";
    int res=0;
    for(int i=0;i<m;++i)
    {
        scanf("%d%d%d",&x,&y,&r);
       // cout << "r:" << r << endl;
        //printf("r:%d\n",r);
        res+=dfs_Trave(x,y,r);
    }
 
    printf("%d\n",res);
    return 0;
}
 
void add(int index)
{
    for(int i=0;i<n;++i)
        if(i!=index)
        {
            if(cir[index].r>(sqrt(pow(cir[i].x-cir[index].x,2)+pow(cir[i].y-cir[index].y,2))))
                adj[index].push_back(i);
        }
}
 
int dfs(int index)
{
    //cout << "jinbulai?\n";
    int sum=1;
    vis[index]=true;
    for(int i=0;i<adj[index].size();++i)
    {
        int v=adj[index][i];
        if(vis[v]==false)
            sum+=dfs(v);
    }
    return sum;
 
}
 
int dfs_Trave(int x,int y,int r)
{
   // cout << "r:" << r << endl;
    int sum=0;
    for(int i=0;i<n;++i)
    {
        double L=sqrt(pow(x-cir[i].x,2)+pow(y-cir[i].y,2));
      //  cout << "length:" << L << endl;
        if(r>L)
        {
           // cout << "error: \n";
            if(vis[i]==false)
                sum+=dfs(i);
        }
    }
    return sum;
}


 

李白打酒加强版

【题目描述】话说大诗人李白,一生好饮。幸好他从不开车。
一天,他提着酒壶,从家里出来,酒壶中有酒 2 斗。他边走边唱:
无事街上走,提壶去打酒。
逢店加一倍,遇花喝一斗。
这一路上,他一共遇到店 N 次,遇到花 M 次。
已知最后一次遇到的是花,他正好把酒喝光了。
请你计算李白这一路遇到店和花的顺序,有多少种不同的可能?
注意:壶里没酒( 0 斗) 时遇店是合法的,加倍后还是没酒;但是没酒时遇花是不合法的。

【输入格式】输入包含多组测试数据。
第一行为T,表示存在T组测试数据,T不超过30。
对于每组测试数据,输入两个整数N 和M.
1 ≤ N, M ≤ 100。

【输出格式】

输出一个整数表示答案。由于答案可能很大,输出模1000000007 的结果。

【输入样例 】

1
5 10
【输出样例 】

14

【思路】:这是一道动态规划题,设f[i][j][k]表示走到了第i个位置,遇到了j个花,还剩k斗酒的合法方案数.

初始化很简单就是f[0][0][2]=1,因为一开始酒的数量是2

假如共遇到店n次,遇到花m次:

那么答案就是f[n+m-1][m-1][1],这是很容易理解的,因为我们共需要遇到m次花且最后一次一定是花,则走到倒数第二个位置时一定已经遇到了m-1个花,且由于遇到花后酒的数量会减少1,所以走到倒数第二个位置时酒的数量也必须是1.

下面开始进行状态转移方程的推导:

首先我们有必要对酒的奇偶性进行讨论,因为当走到第i个位置时酒的数量为奇,则第i个位置不可能为店,因为无论是奇数还是偶数,乘以2得到的数都是一个偶数,所以只有当k是偶数时第i个位置才可能是店,假如第i个位置是店,那么这个时候在第i-1个位置的酒的数量就是k/2,而遇到花的数量跟第i-1个位置是一样的都是j,这个很好理解,下面再看一下第i个位置是花的情况,那么第i-1个位置的酒的数量一定是k+1,因为在第i个位置消耗了1,而到达第i个位置遇到的花的数量就要比第i-1个位置遇到花的数量多1,因为我们现在是在假设第i个位置是花。
 

#include<iostream>

using namespace std;
const int N=113;
const int mod=1e9+7;
long long f[N*2][N][N];//f[i][j][k]表示走到了第i个位置,遇到了j个花,还剩k斗酒的合法方案数 
int main()
{
	f[0][0][2]=1;//初始化
	int n,m;
	cin>>n>>m;
	for(int i=1;i<n+m;i++)
	for(int j=0;j<m;j++)
	for(int k=0;k<=m;k++)//酒的数量不能超过花的数量,否则就算之后一直是花也喝不完 
	{
		if(!(k&1))//k是偶数,则第i个位置可以是店,否则不可以是店
			f[i][j][k]=(f[i][j][k]+f[i-1][j][k>>1])%mod;
		if(j>=1)//无论k是奇数还是偶数,第i个位置都可以是花 
			f[i][j][k]=(f[i][j][k]+f[i-1][j-1][k+1])%mod;
	}
	printf("%lld",f[n+m-1][m-1][1]);
	return 0; 
}

【题目描述】

这天,小明在砍竹子,他面前有 n 棵竹子排成一排,一开始第 i 棵竹子的高度为 hi.

他觉得一棵一棵砍太慢了,决定使用魔法来砍竹子。

魔法可以对连续的一段相同高度的竹子使用,假设这一段竹子的高度为H,

那么使用一次魔法可以把这一段竹子的高度都变为:

其中 ⌊x⌋ 表示对 x 向下取整。

小明想知道他最少使用多少次魔法可以让所有的竹子的高度都变为1。

【输入格式】

第一行为一个正整数 n,表示竹子的棵数。

第二行共 n 个空格分开的正整数 hi,表示每棵竹子的高度。

20%的测试数据:n ≤ 1000; hi ≤ 10^6。

100%的测试数据:n ≤ 2 × 10^5; hi ≤ 10^18。

【输出格式】

一个整数表示答案。

【输入样例】

6

2 1 4 2 6 7

【输出样例】

5

【数据范围与提示】

其中一种方案:

2 1 4 2 6 7

→ 2 1 4 2 6 2

→ 2 1 4 2 2 2

→ 2 1 1 2 2 2

→ 1 1 1 2 2 2

→ 1 1 1 1 1 1

共需要 5 步完成。

分析:先来说一个贪心策略,我们优先选择砍所剩竹子中高度最大的竹子,因为无论我们怎么砍高度低的竹子都不可能使得高度低的竹子高度变高,从而能够和高度高的竹子一块被砍,相反的,我们砍完高度高的竹子后由于高度变低,所以可能会跟原来高度低的竹子一块被砍,所以这种贪心策略显然是正确的。而且高度相同的连续竹子一定要放在一起砍,这是显然的,一次可以砍完的事情为什么非要分几次呢?有了这个策略我们再来看一下一棵竹子最多会被砍多少次,你可以先按竹子高度最高1e18来算,发现他经过6次就可以砍成高度为1的竹子,也就是说每棵竹子被砍的次数都不会超过6.

那我们可以开一个优先队列,里面存pair类型,第一维是竹子的高度,第二维是竹子的编号,那么我们先把所有的竹子放入优先队列,每次取出一棵竹子,并记录其编号,直到取到一棵竹子高度不等于前一棵竹子的高度或者编号与前一棵竹子编号不是相连的,这个时候就将砍的次数+1.注意由于高度是从高到低排的,我没有用结构体排序,所以pair两维都是按照从高到低的规则来进行排序的,所以先出队列的就是编号较大的,只需要进行一下判断高度是否相等以及编号是否连续即可。还有一点需要注意的就是,我们每次取出队头竹子,然后把这个竹子的高度和编号记录一下,用于判断后续竹子是否可以和当前竹子一块被砍,然后就可以直接把当前竹子砍掉并讲砍完后的高度连同其编号一同放入优先队列(前提是竹子被砍后高度不为1),直至队空为止。
 

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<set>
#include<vector>
#include<map>
#include<queue>
#include<cmath>
using namespace std;
typedef pair<long long,int>PII;
priority_queue<PII>q;
int main()
{
    int n;
    cin>>n;
    long long ans=0;
    for(int i=1;i<=n;i++)
    {
        long long t;
        scanf("%lld",&t);
        if(t!=1)
        q.push({t,i});
    }
    while(!q.empty())
    {
        long long t=q.top().first;//记录当前竹子的高度 
        int r=q.top().second;//记录当前竹子的编号 
        q.pop();
        long long l=sqrt(t/2+1);
        if(l!=1)
            q.push({l,r});
        while(!q.empty()&&q.top().first==t&&q.top().second==r-1)
        {
            r--;
            q.pop();//将队首竹子pop 
            if(l!=1)
                q.push({l,r});//将砍完后的竹子放入优先队列 
        }
        ans++;//当发现竹子高度不一致或者编号不连续时记录一次砍的次数 
    }
    printf("%lld",ans);
    return 0;
}
 

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值