2022蓝桥杯省赛C++B组

2022蓝桥杯省赛C++B组

九进制转十进制

我的思路:
代码实现:
#include <iostream>

using namespace std;

void transToTen(int o,int n){
    int ans=n%10;
    int mul=o;
    while(n){
        n/=10;
        ans+=n%10*mul;
        mul*=o;
    }
    printf("%d",ans);
}

int main()
{
    int n,o;
    /*cin>>o>>n;
    transToTen(o,n);*/
    transToTen(9,2022);
    return 0;
}

顺子日期

我的思路:

一个个数

代码实现:
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
int main()
{
    printf("14");
    return 0;
}

刷题统计

我的思路:
代码实现:
#include <iostream>
 
using namespace std;
 
int main()
{
    long long a,b,n;
    cin>>a>>b>>n;
    long long ans=0;
    long long value;
    long long weekSum=a*5+b*2;
    //优化计算
    long long temp=n/weekSum;
    ans+=7*temp;
    value=n-temp*weekSum;
    while(value>0)//剩下的题数
        {
        ans++;
        if(ans%7!=6&&ans%7!=0){//不是周六周日
            value-=a;
        }
        else{
            value-=b;
        }
    }
    printf("%ld",ans);
    return 0;
}

修剪灌木

我的思路:

根据例子枚举,推出公式

代码实现:
#include <iostream>
using namespace std;
int main()
{
    int n;
    cin>>n;
    int temp=n/2;
    for(int i=1;i<=n;i++){
        if(i<=temp){
            cout<<n*2-2*i<<endl;
        }
        else{
            cout<<n*2-2*(n+1-i)<<endl;
        }

    }
    return 0;
}

X进制减法

题目描述

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

输入格式

第一行一个正整数 N,含义如题面所述。
第二行一个正整数 Ma,表示 X 进制数 A 的位数。
第三行 Ma 个用空格分开的整数,表示 X 进制数 A 按从高位到低位顺序各个数位上的数字在十进制下的表示。
第四行一个正整数 Mb,表示 X 进制数 B 的位数。
第五行 Mb 个用空格分开的整数,表示 X 进制数 B 按从高位到低位顺序各个数位上的数字在十进制下的表示。
请注意,输入中的所有数字都是十进制的。
30%的测试数据:2≤N≤10,1≤Ma,Mb≤8。
100%的测试数据:2≤N≤1000,1≤Ma,Mb≤100000,B≤A。

输出格式

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

输入样例
11
3
10 4 0
3
1 2 0
输出样例
94
数据范围与提示

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

我的思路:

先来看一看例子,最低数位为二进制,第二数位为十进制,第三数位为八进制;然后X进制数321变为十进制65;首先看最低数位1,变为十进制还是1;第二数位为十进制,但实际上,当最低位为2时,第二数位加1,所以实际上第二数位的2转为十进制为4;那第三数位,当第二数位为10时,第三数位加1,第二数位为10时,十进制是多少?我们考虑最低数位,是102=20;所以321→65即为3(102)+22+1;

现在我们来概括一下:假设第 i 位为 ai 进制 (从低到高,假设 i=0 为最低位)

定义,img

对于该规则下的进制数 A,其十进制值就是 A_{0}b_{0}+A_{1}b_{1}+L+A_{n}b_{n}

同样的,B的十进制值就是 B_{0}b_{0}+B_{1}b_{1}+L+B_{n}b_{n}

根据题意A-B,得(A_{0}-B_{0})b_{0}+(A_{1}-B_{1})b_{1}+L+(A_{m}-B_{m})b_{m}+A_{m+1}b_{m+1}+L+A_{n}b_{n}

(注意题中A的长度可能大于B)

因为A₀-B₀,A₁-B₁……都已知,是定值;所以我们要让吧b₀,b₁……最小;也就是让进制尽可能小,显然每个位置的进制等于A,B 两个数的较大值 +1(同时保证 A 和 B 在 X 进制下都是合法的,即每一数位上的数字要小于其进制,所以+1)

代码实现:
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;

const int mod = 1e9 + 7;
int a[100010], b[100010], c[100010];//c用来存数位进制
int A, B;
int main()
{
	int n;
	cin >> n;//不重要

	cin >> A;
	for (int i = A; i >=1; i--) cin >> a[i];
	cin >> B;
	for (int i = B; i >=1; i--) cin >> b[i];
	//求进制
	int len = A;//根据题意A>B
	for (int i = len; i >=1; i--) {
		c[i] = 2;
		c[i] = max(c[i], max(a[i], b[i]) + 1);//最接近两数就是最大的那个加一
	}
	//计算A-B
	ll ansA = a[1], ansB = b[1];//最低位不用乘,直接加
	ll temp=1;
	for (int i = 2; i <=len; i++) {
		temp=temp*c[i-1]%mod;//一定要mod,最高位进制不重要
		ansA=(ansA+a[i]*temp)%mod;
		if (i <= B) ansB = (ansB+b[i]*temp)%mod;
    }
	ll ans = (ansA - ansB + mod) % mod;//考虑到负数所以要加mod
	cout << ans << endl;

	return 0;
}

统计子矩阵

知识点:

1.二维前缀和,学习链接:http://t.csdn.cn/ufrkw

2、void *memset(void * s, int ch, size_t n);

功能: 将s指向的一块连续内存中的前 n 个字节的内容全部设置为ch指向的ASII值 (ASII值可以用一个字节 8位来表示)

头文件:

3.双指针算法,学习链接:http://t.csdn.cn/tKODj

我的思路:

通过二维前缀和,可以得到任意子矩阵中的和

二维前缀和:DP[i] [j]表示(1,1)这个点与(i,j)这个点两个点分别为左上角和右下角所组成的矩阵内的数的和

DP[i][j]=DP[i-1][j]+DP[i][j-1]-DP[i-1][j-1]+map[i][j]

得到二维前缀和后,采用双指针方法遍历找寻符合条件的,先固定左边界,移动右边界,固定上边界,移动下边界,即从左上角到右下角,这里可以自己画图结合代码来理解。

代码实现:
#include <iostream>
#include<cstring>
using namespace std;
int n,m,k;
int dp[505][505]={0};
long long ans=0;
int main()
{
    memset(dp,0,sizeof(dp));//初始化数组
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            int t;
            cin>>t;
            dp[i][j]=dp[i][j-1]+dp[i-1][j]-dp[i-1][j-1]+t;//计算二维前缀和
        }
    }
    //开始遍历,采用双指针方法
    //先固定左边界,移动右边界,固定上边界,移动下边界(左上角到右下角)
    for(int l=1;l<=m;l++)//枚举矩阵的左边
    {
        for(int r=l;r<=m;r++)//枚举矩阵的右边
        {
            for(int i=1,j=1;i<=n;i++)//根据已有的边界,从上到下遍历
            {//j是上边界,i是下边界
                while(j<=i&&dp[i][r]-dp[i][l-1]-dp[j-1][r]+dp[j-1][l-1]>k)
                    j++;//比k大不符合条件,上边界下移
                //符合条件则不移动上边界
                if(j<=i) ans+=i-j+1;//大的子矩阵符合,包含在里面的也符合
            }

        }
    }
    cout<<ans<<endl;
    return 0;
}

积木画

我的思路:

参考链接:AcWing 4406. 积木画——递推算法(详细) - AcWing

看到题目,应该想到离散数学中的高级计算递推算法,我们可以设f(n)为2xN画布的拼接方式数,这里可以通过自己画图从高位到低位列举出,如何构成f(n):从f(n-1)到f(n)有一种方法,从f(n-2)到f(n)有一种方法,从f(n-3)开始到f(0)都会有两种方法,因为加入了L型积木,它是可以进行翻转的,需要特别注意的是不能够有重复,所以f(n)=f(n-1)+f(n-2)+2x[f(n-3)+f(n-4)+···+f(0)]

那么初始条件我们可以知道f(0)=1(只有不放一种方案),f(1)=1,f(2)=2

又因为n很大,所以计算机进行递推肯定会超时,所以我们应该用“数组+for循环”来进行,又因为数组的个数很大,所以要放到全局变量,因为全局变量时存放在静态存储区,局部变量是存放在堆栈,可能空间不够,就会导致溢出。

递推公式的”[f(n-3)+f(n-4)+···+f(0)]“这部分通过前缀和来求,可以减少时间复杂度。可能很多人很疑惑这部分为什么这么写?因为 L 型积木是成对出现的,我们可以在里面穿插任意 I 型积木,随着穿插的 I 型积木数量增多,就相当于 2×3、2×4、2×5⋯2×3、2×4、2×5⋯ 的积木,因为每次穿插后都是不和前面重复的,所以我们要一直到f(0),不然会遗漏
于是,这道题的题意就变成了:给定一个 2×N 的画布,以及 2×1、2×2、2×3⋯2×1、2×2、2×3⋯ 的积木,其中长度大于 3 的积木,都有两种,求恰好把画布放完的方案数。

代码实现:
#include <iostream>
using namespace std;
#define mod 1000000007
long long f[10000005];
long long sum=0;
int main()
{
    int n;
    cin>>n;
    f[0]=f[1]=1;
    f[2]=2;                 
    for(int i=3,j=0;i<=n;i++,j++){
        sum+=f[j];//0到(i-3)前缀和,减少计算
        f[i]=(f[i-1]+f[i-2]+sum*2)%mod;
    }
    cout<<f[n]<<endl;
    return 0;
}

扫雷

知识点:

用队列,暴力搜索

哈希表:关键值通过散列函数处理后得到哈希值,哈希值根据哈希表长度求模得到存储中的相应位置key,一般为避免冲突,哈希表长度在保证能装下所有数据,还要为质数。学习链接:http://t.csdn.cn/MfAyr

我的思路:

参考链接:AcWing 4407. 扫雷——y总巧妙哈希讲解,没懂的小伙伴快进来! - AcWing

用结构体来封存地雷和火箭,然后根据它们的坐标用哈希表存起来,然后枚举排雷,因为会递接的覆盖,所以用队列来保存,每个雷爆炸后都要标致访问过,最后进行统计。

代码实现:
#include <iostream>
#include<queue>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=5e4+5,Len=1e6+7,X=1e9+1;//N-爆炸物数,Len-哈希表长度,X-构造散列函数
//爆炸物结构体
struct node{
    int x,y,r;
}b[N];
ll h[Len];//哈希表
int id[Len];//每个下标key对应的地雷编号
bool visted[N];//记录地雷是否被访问
//得到每个坐标的哈希值
ll getHash(int x,int y){
    return (ll)x*X+y;
}
//找到该坐标被哈希数组存储的下标key
int find(int x,int y){
    ll hs=getHash(x,y);
    int key=(hs%Len+Len)%Len;// 映射到哈希数组内部.+Len是为了避免负数
    // 如果该位置已经被占用并且不等于我们要求的哈希值,要往后找到相应位置
    while(h[key]!=-1&&h[key]!=hs){
        key++;
        if(key==Len) key=0;// 哈希表走到末尾,又从0开始
    }
    return key;
}
// 判断x,y为圆心,半径为r的圆是否包含xx,yy
bool check(int x,int y,int r,int xx,int yy){
    int d=(x-xx)*(x-xx)+(y-yy)*(y-yy);
    return d<=r*r;
}
//用队列枚举每个范围内的地雷
void bfs(int pos){
    queue<int> q;
    q.push(pos);
    visted[pos]=true;//标致当前地雷已经被访问
    while(!q.empty()){
        int t=q.front();
        q.pop();
        int x=b[t].x,y=b[t].y,r=b[t].r;
        // 枚举地雷爆炸范围,从(x-r, y-r)到(x+r, y+r)
        for(int xx=x-r;xx<=x+r;xx++){
            for(int yy=y-r;yy<=y+r;yy++){
                int key=find(xx,yy);// 找到该坐标点对应的哈希下标
                // 该点是不是地雷,有没有被访问过,能不能炸到
                if(id[key]&&!visted[id[key]]&&check(x,y,r,xx,yy)){
                    visted[id[key]]=true;//标致已经访问
                    int pos=id[key]; // 获得对应地雷编号
                    q.push(pos);//入队继续进行搜索
                }

            }
        }
    }
}
int main()
{
    int n,m;
    cin>>n>>m;
    memset(h,-1,sizeof(h));//哈希表初始值为-1
    int x,y,r;
    for(int i=1;i<=n;i++){// 读入地雷,存入哈希表
        cin>>x>>y>>r;
        b[i]={x,y,r};
        int key=find(x,y);//找到坐标对应哈希表的位置
        if(h[key]==-1) //哈希表位置空则插入存储
            h[key]=getHash(x,y);
        // id数组没有被标记或者找到了同一个坐标点更大半径的地雷
        if(!id[key]||b[id[key]].r<r)
            id[key]=i;
        //不用担心会被覆盖没记到数,后面是遍历每个地雷计数的
    }
    // 枚举导弹,进行排雷
    for(int i=1;i<=m;i++){
        cin>>x>>y>>r;
        // 从(x-r, y-r)枚举到(x+r, y+r)
        for(int xx=x-r;xx<=x+r;xx++){
            for(int yy=y-r;yy<=y+r;yy++){
                int key=find(xx,yy);
                // 如果该点有地雷,没有被访问过,能被炸到
                if(id[key]&&!visted[id[key]]&&check(x,y,r,xx,yy))
                    bfs(id[key]);//搜索该地雷又能炸到几个地雷
            }
        }
    }

    ll ans=0;
    //遍历所有地雷,看有没有被访问
    for(int i=1;i<=n;i++){
        int key=find(b[i].x,b[i].y);// 获得坐标点对应哈希下标
        int pos=id[key];// 哈希下标对应的地雷编号
        if(visted[pos])//若被访问过则加1
            ans++;

    }
    cout<<ans;
    return 0;
}

李白打酒加强版(动态规划状态设计)

我的思路:

动态规划题,找状态,进行设计,列状态转移方程,然后进行边界设计

状态设计:dp[i][j][k]的值表示遇到i家店,j朵花,酒壶中还剩k斗酒的可能情况数

状态情况:想要推出dp[i][j][k]有两种情况,一是逢店加一倍 (i-1)+1,(k/2)*2,即dp[i-1][j][k/2](i>=1&&k%2==0)

​ 二是遇花喝一斗 (j-1)+1,(k+1)-1,即dp[i][j-1][k+1](j>1)

状态转移方程:dp[i][j][k]=dp[i-1][j][k/2](i>=1&&k%2==0) + dp[i][j-1][k+1](j>=1);
边界设计:除了dp[0][0][2]=1,其他元素全为0,因为起初只有一种情况;
他一共遇到店 N 次,遇到花 M 次。已知最后一次遇到的是花, 他正好把酒喝光了;所以
最后一次肯定遇到的是花,那么最后的结果便是dp[N][M-1][1];
并且酒壶中酒的容量不能超过M;

代码实现:
#include <iostream>
#include<cstring>
using namespace std;
const int mod=1e9+7;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin>>t;
    while(t--){
        int n,m;
        cin>>n>>m;
        int dp[n+1][m+1][m+1];
        //dp[i][j][k]的值表示遇到i家店,j朵花,酒壶中还剩k斗酒的可能情况数
        memset(dp,0,sizeof(dp));
        dp[0][0][2]=1;//初始值
        //状态转移,由后推前
        for(int i=0;i<=n;i++){
            for(int j=0;j<=m;j++){
                for(int k=0;k<=m;k++){
                    int val=dp[i][j][k];
//如果是 &val=dp[i][j][k],就不用再次 dp[i][j][k]=val,因为直接指针指向内存改变
                    //逢店加一倍,i+1,k*2
                    if(i>=1&&k%2==0)
                        val=(val+dp[i-1][j][k/2])%mod;
                    //遇花喝一斗,j+1,k-1
                    if(j>=1)
                        val=(val+dp[i][j-1][k+1])%mod;
                    dp[i][j][k]=val;
                }
            }
        }
        cout<<dp[n][m-1][1]<<endl;//由题最后一次肯定遇花喝酒
    }
    return 0;
}

砍竹子(贪心,优先队列,pair)

知识点:

int 最大值10^9

long long 最大值10^18

我的思路1:

题目要求用最少的次数来把所有竹子砍成高度1,那么根据贪心,我们每次都要选取最高的竹子来砍,同时如果有连续的编号,高度相同,那么一并砍掉,记录使用魔法一次。为了每次都砍到最高的竹子,那么使用优先队列来存储,又因为考虑到连续编号,所以用pair把高度和编号绑定起来。然后循环砍竹子,直到所有竹子的高度都变成1。

分析一下复杂度:每棵竹子最多进6次队列,因为竹子高度最高1e18来算,发现经过6次就可以砍成高度为1的竹子,也就是说每棵竹子被砍的次数都不会超过6,共有n棵竹子,由于队列中最多同时有n棵竹子,所以每次入队都是o(logn)的(优先队列快速排序),所以说总的复杂度就是6*n*logn,最后结果是超过1s,但是通过了大部分数据。

代码实现1:
#include <iostream>
#include<queue>
#include<cmath>
using namespace std;
typedef long long ll;
priority_queue<pair<ll,int>> q;//默认高度从大到小排序,再到编导从大到小
ll ans;
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        ll h;
        cin>>h;
        if(h!=1)//高度为1就不用入队处理了
            q.push({h,i});
    }
    while(!q.empty()){
        ll h=q.top().first;//记录最高竹子
        int id=q.top().second;
        q.pop();//最高竹子出队
        ll dh=sqrt(h/2+1);
        //砍完后高度不为1重新入队
        if(dh!=1)
            q.push({dh,id});
        //把高度一致,编号连续的一同砍掉,这里编号从大到小
        while(!q.empty()&&q.top().first==h&&q.top().second==id-1){
            id--;//跳到当前竹子
            q.pop();//出队修改高度
            if(dh!=1)//高度不为1重新入队
                q.push({dh,id});
        }
        ans++;//没有连续相同高度的结束这一次魔法
    }
    cout<<ans;
    return 0;
}

我的思路2:

下面我们来对上面的方法进行优化,就是我们可以先存下来第i棵树还剩j次砍为1时的高度记录为f[i][j],说的有点绕,我举个例子:比如第i棵树高度为15,那么第一次砍完后高度为2,第二次砍完后高度为1。那么还剩一次就能砍为1的高度为2,还剩两次就能砍为1的高度为15.明白了f数组的含义后我们就能够对上面的问题进行简化了,首先可以知道的一点就是当且仅当两棵树在同一层才有可能被同时砍,能被同时砍不仅要求在同一层,还需要要求高度相同且编号相邻,所以我们可以直接遍历每一层,只要发现有相邻的两棵树高度相同我们的砍树次数就可以减少1,一开始的砍树次数就是所有的树都一次一次地砍为1所需要的次数和,利用这样的方法我们就可以优化刚才进入优先队列的o(logn)的复杂度,所以总的复杂度就是O(6n)

代码实现2:
#include <iostream>
#include<cmath>
using namespace std;
typedef long long ll;
ll f[200005][10];//f[i][j]表示第i个数还剩j次砍为1的高度
ll l[10];//记录当前竹子砍i-1次后剩余的高度
ll ans;
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        ll h;
        cin>>h;
        int t=0;
        while(h!=1){
            l[++t]=h;//记录当前竹子砍i-1次后剩余的高度
            h=sqrt(h/2+1);
        }
        ans+=t;//先统计每个竹子单独砍为1的次数
        for(int j=1;t>0;j++,t--){
            f[i][j]=l[t];
        }
    }
    //开始减去那些可以使用魔法的
    for(int i=1;i<=6;i++){//每个竹子不超过砍6次
        for(int j=2;j<=n;j++){
            if(f[j][i]&&f[j][i]==f[j-1][i])
                ans--;//相同高度且编号连续
        }
    }
    cout<<ans;
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值