第十二届蓝桥杯b组省赛C/C++ 个人笔记

目录

试题A:空间

试题B:卡片

试题C:直线

试题D:货物摆放

试题E:路径

F 时间显示

G 砝码称重

H 杨辉三角形


试题A:空间

1MB等于1024KB,1KB=1024B,B=byte 字节,1B=8bits,bits是位数,所以32位二进制整数就占4个字节,

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

int main()
{
    cout<<256*1024*1024*/4<<endl;
    return 0;
}

最后答案为: 67108864.

试题B:卡片

这题直接模拟就好,用一个数组来存0~9的个数,然后从1开始枚举,每个数分别把每个位拿出来判断,当判断到当前数字不够用的时候就判false,然后枚举到第i个不能成立时,输出i-1即为答案。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int st[10];
bool check(int x)
{
    while(x)
    {
        int t=x%10;
        x/=10;
        if(--st[t]<0)return false;
      
    }
    return true;
}
int main()
{
    for(int i=0;i<10;i++)st[i]=2021;
    for(int i=1; ;i++)
    if(!check(i))
    {
    cout<<i-1<<endl;
    return 0;
    }
}

答案:3181

试题C:直线

枚举每一条直线,用结构体来存每一条边的斜率和截距,然后斜率小的排在前,斜率相同则截距小的排在前,把每一条边按从小到大排序好,遍历每一条直线, 然后判断斜率不同的有多少,fabs为用来求浮点数的绝对值,浮点数的存储在计算机内是有误差的,判断两个double数是否相等,只要这两个数的差在1e-8以内就算相等。最后还要记得加上斜率为0的20条竖线。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int N =200000;
int n;
struct Line
{
 double k,b;
 bool operator<(const Line&t)const 
 {
  if(k!=t.k)return k<t.k;
  return b<t.b;
 }
}l[N];
int main()
{
 for(int x1=0;x1<20;x1++)
 for(int y1=0;y1<21;y1++)
 for(int x2=0;x2<20;x2++)
 for(int y2=0;y2<21;y2++)
 if(x1!=x2)
 {
  double k=(double)(y2-y1)/(x2-x1);
  double b=y1-k*x1;
  l[n++]={k,b};
 }
 sort(l,l+n);
 int res=1;
 for(int i=1;i<n;i++)
  if(fabs(l[i].k-l[i-1].k)>1e-8||fabs(l[i].b-l[i-1].b)>1e-8)
  res++;
  cout<<res+20<<endl;
  return 0;
}

 答案:40257

试题D:货物摆放

 这题可以先去求n的所有约数,然后三重循环枚举一下哪些相乘等于n的。

#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;

typedef long long LL;
int main()
{
    LL n;
    cin>>n;
    vector<LL>d;
    for(LL i=1;i*i<=n;i++)
    if(n%i==0)
    {
        d.push_back(i);
        if(n/i!=i)d.push_back(n/i);
    }
    int res=0;
    for(auto a:d)
    for(auto b:d)
    for(auto c:d)
    if(a*b*c==n)
     res++;
     cout<<res<<endl;
     return 0;
}

试题E:路径

,那么每个点最多有 21*2=42 条边,2021个点*42条边那么这题就是个很经典的最短路问题,这里我使用spfa算法解决

#include <iostream>
#include <cstring>
#include <cmath>

#include <algorithm>
using namespace std;
const int N = 2200,M=N*50;
int n;
int h[N],e[M],w[M],ne[M],idx;
int q[N],dist[N];
bool st[N];
int gcd(int a,int b)//求最大公约数
{
    return b?gcd(b,a%b):a;
}
void add(int a,int b,int c)//创建邻接表,此题为稀疏图所以用邻接表
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void spfa()
{
    int hh=0,tt=0;
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    q[tt++]=1;
    st[1]=true;
    while(hh!=tt)
    {
        int t=q[hh++];
        if(hh==N)hh=0;
        st[t]=false;
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[t]+w[i])
            {
              dist[j]=dist[t]+w[i];
              if(!st[j])
              {
                  q[tt++]=j;
                  if(tt==N)tt=0;
                  st[j]=true;
              }
            }
            
        }
    }
}
int main()
{
    n=2021;
    memset(h, -1, sizeof h);
    for(int i=1;i<=n;i++)//枚举每个点,点的数量
    for(int j=1;j<=n;j++)
    {
        int d=gcd(i,j);//求出最大公约数
        if(i!=j)
        {
            if(fabs(i-j)<=21)
            {
                add(i,j,i*j/d); 两个数相乘除以最大公约数就可以求出最小公倍数
               
            }
        }
    }
    spfa();
    printf("%d\n",dist[n]);
    return 0;
    
}

F 时间显示

 因为不用考虑年月日,只需要考虑当天的时间即可

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

int main()
{
   LL n;
   cin>>n;
   n/=1000;//因为不用考虑毫秒就把毫秒除掉
   n%=86400;//一天是86400秒,因为只求当天的时间把今天之前的时间除了,取余就是今天的秒数
   int h=n/3600;//小时数
   n%=3600;//除去小时后剩下的时间
   int m=n/60;//除以60便是分钟
   int s=n%60;//取余便是除去小时很分钟后剩下的时间
   printf("%02d:%02d:%02d\n",h,m,s);
   return 0;
   
   
}

G 砝码称重

 背包问题,dp的状态可以分为,不选wi 和选+wi(即放在天平左边),选-wi(即放在天平右边)

#include <bits/stdc++.h>

using namespace std;

const int N = 110, M = 2e5 + 10;
int sum;
int n;
int w[N];
bool f[N][M];

int main() {

    cin>>n;
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &w[i]);
        sum+=w[i];
    }

    f[0][0]=true;

    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])];
                //只要有一个非空,f[i][j]就非空,使用abs避免了数组下标为负数的情况
    int ans = 0;
    for (int i = 1; i <=sum;i++)
        if(f[n][i])ans++;//不为零说明可以选出这个质量的砝码

    cout << ans;

    return 0;
}

H 杨辉三角形

本题是思维题,考的是数学+二分查找,杨辉三角是左右对称的,所以我们只用看一半即可

在这里插入图片描述

 斜着看每一条斜线我们可以从图中发现除了第一行外,每一行都是单调递增的,又因为杨辉三角就是组合数,所以每一行的最小值可以表示C(k 2k)  k 是当前行(横着)第几个数如图所示

 有图我们可以得出以下性质:
        1. 每一斜行从上到下递增
        2. 每一横行从中间到两边依次递减

n最大1e9,C(34, 17) > 1e9, C(32, 16) < 1e9,因此只要枚举前16个斜行即可!

因为是斜着的每一行是单调递增的,所以我们可以用二分来找到目标值 nn, 又因为每一斜行也是单调递增的,所以我们从最里面开始往外找

上边界 l=2k
下边界 r=n (如果内层斜行都没找到,一定出现在第2行,因为Cn1=n)

所以我们可以枚举每一斜行的开头元素(最小元素)k, 从 16 开始枚举

二分的check就是 如果 Ckmid>=n,取左区间,否则取右区间

当我们找到了这个数的时候即C(r,k)==n (r为底数)
我们通过找规律发现 r 为前面共多少行(不算这一行),第一行有 1 个数,第二行有 2 个数,第三行有 3个数,第 n 行有 n 个数,也就是前面一共有 r(1+r)2 个数, k 为这一行第几个(下标都从 0 开始),所以这个数是第 r(1+r)/2 +k+1 个数

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

int n;

// C(a, b) = a!/b!(a-b)! = a * (a-1) .. b个 / b!
LL C(int a, int b){
    LL res = 1;
    for(int i = a, j = 1; j <= b; i --, j ++){
        res = res * i / j;
        // 大于n已无意义,且防止爆LL
        if(res > n) return res;
    }
    return res;
}

bool check(int k){
    // 二分该斜行,找到大于等于该值的第一个数
    // 左边界2k,右边界为max(l, n)取二者最大即可!
    int l = 2 * k, r = max(n, l);
    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;
    // C(r, k)的从0开始的顺序!
    cout <<   (r + 1) * r / 2 + k + 1 << endl;
    return true;
}

int main(){
    cin >> n;
    // 从第16斜行枚举即可!
    for(int k = 16; ; k --)
        if(check(k)) break;
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值