2021第十二届蓝桥杯省赛

2021第十二届蓝桥杯省赛

填空题:

1.计算空间(考察单位转换)

 

k=10^3 Kilo(千)

M=10^6 Mega(百万)

G=10^9 Giga(十亿)

T=10^12 Tera(兆)

P=10^15 Peta(千兆)

E=10^18 Exa(百京)

B=10^21 Bronto(十垓)

1TB=1024GB
1GB=1024MB
1MB=1024KB
1KB=1024Byte
注:Byte就是B也就是字节
KB是千字节
MB是兆
GB是千兆
TB是千千兆

https://www.lanqiao.cn/problems/1445/learning/?subject_code=1&group_code=4&match_num=12&match_flow=1&origin=cup

思路:

256MB=256*210 *210 B

32位=32/8=4B

所以答案是:(256*210 *210 )/4

#include <iostream>
using namespace std;
int main()
{
  cout<<1024*1024*64;
  // 请在此输入您的代码
  return 0;
}

9.货物摆放

https://www.lanqiao.cn/problems/1463/learning/?subject_code=1&group_code=4&match_num=12&match_flow=1&origin=cup

长宽高肯定是n的因子。

暴力枚举三重循环长宽高都从1~n然后当长*宽*高=n时ans+=1一定会超时

长宽高枚举都是从n的因子中取所以先求因子。

输出答案:

#include <bits/stdc++.h>
using namespace std;
int main()
{
 cout<<2430;
  // 请在此输入您的代码
  return 0;
}
计算思路:

#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
vector<ll>t;//存放n的因子
int main()
{
  ll ans=0;
  ll n;
  cin>>n;
ll h=sqrt(n+0.5);//向上取整开方

//找n的所有因子
for(int i=1;i<=h;i++){
  if(n%i==0){
    t.push_back(i);
    if(i*i==n)continue;
    else t.push_back(n/i);
  }
}

//t[i],t[j],t[k]为长宽高
for(int i=0;i<t.size();i++){
  for(int j=0;j<t.size();j++){
    for(int k=0;k<t.size();k++){
      if(t[i]*t[j]*t[k]==n)ans+=1;
    }
  }
}
cout<<ans;
  // 请在此输入您的代码
  return 0;
}

8.路径

https://www.lanqiao.cn/problems/2383/learning/?subject_code=1&group_code=4&match_num=12&match_flow=1&origin=cup

答案:

cout<<10266837;

计算思路:

考察算法:

最短路算法+最小公倍数算法

最短路算法任选一个下面是迪杰斯特拉算法

i,j最小公倍数=i*j/gcd(i,j);

gcd为最大公因数算法

自环在最短路里没有用可不管

重边取最小

//朴素版djstla
#include<bits/stdc++.h>
using namespace std;
const int N=2100;
int n,m;//n个顶点m条边
int g[N][N];//每条边的权值
bool st[N];//st[i]表示第i个点已经访问
int d[N] ; //本题中st[i]为从第一个点到第i个点的最小值
int djs(){
    memset(d,0x3f,sizeof(d));
   d[1]=0;//到自己距离是 0
   for(int i=0;i<n-1;i++)//找n-1回
   {
       int t=-1;//为找寻那个可以确定的点
       for(int j=1;j<=n;j++){
           if((!st[j])&&(t==-1||d[t]>d[j])){
               t=j;
           }
       }
       //用找到的t那个点尝试更新所有点如果能使距离减小
       for(int j=1;j<=n;j++){
           d[j]=min(d[j],d[t]+g[t][j]);
       }
       st[t]=true;
   }
   
   
   if(d[n]==0x3f3f3f3f)return -1;
   else  return d[n];
}
int gcd(int a,int b){
  if(b==0)return a;
  else return gcd(b,a%b);
}
int main(){
    n=2021; 
    memset(g,0x3f,sizeof(g));
    for(int i=1;i<=n;i++){
      for(int j=1;j<=n;j++){
        if(abs(i-j)<=21)g[i][j]=(i*j)/gcd(i,j);
      }
    }
    cout<<djs();
    return 0;
}

3.直线

https://www.lanqiao.cn/problems/1449/learning/?subject_code=1&group_code=4&match_num=12&match_flow=1&origin=cup

#include<bits/stdc++.h>
using namespace std;
bool h[21];
bool l[21];
// 求最大公约数
int gcd(int a, int b) {
    return b == 0? a : gcd(b, a % b);
}
int main() {
    int num = 0;
    set<pair<pair<int, int>, int>> ans;
    for (int i = 0; i <= 19; i++) {
        for (int j = 0; j <= 20; j++) {
            for (int x = 0; x <= 19; x++) {
                for (int y = 0; y <= 20; y++) {
                    if (i == x && j == y) continue;
                    if (j == y) {
                        if (!h[j]) {
                            h[j] = true;
                            num++;
                        }
                        continue;
                    }
                    if (i == x) {
                        if (!l[i]) {
                            num++;
                            l[i] = true;
                        }
                        continue;
                    }
                    int dx = x - i;
                    int dy = y - j;
                    int d = gcd(dx, dy);
                    dx /= d;
                    dy /= d;
                    int intercept = j * dx - i * dy;
                    ans.insert({{dx, dy}, intercept});
                }
            }
        }
    }
    cout << ans.size() + num << endl;
    return 0;
}

浮点数精度问题一般少用除法,可以同时去分母

程序题:

10.卡片问题

https://www.lanqiao.cn/problems/2383/learning/?subject_code=1&group_code=4&match_num=12&match_flow=1&origin=cup

Ck2+k>=n

法1找公式
 #include <bits/stdc++.h>
using namespace std;
int main()
{
    
 //k^2+k>=2n
 int n;
 cin>>n;
 int ans;
 for(int i=1;i<=sqrt(2*n);i++){
if(i*i+i>=2*n){
  ans=i;
  break;
}
 }
 cout<<ans;
  return 0;
}
法二动态规划
#include <iostream>
using namespace std;
const long long int N=100006;
//数组容量有限制最多开10的八次方
//所以要对总数最大值公式求解S=n+Cn 2  =n+(n*(n-1))/2=(n+n*n)/2
//所以开个10的五次方即可
//d[i]表示i种牌的最大搭配人数,转移方程:dp[i]=dp[i-1]+i 第i种牌可以和前i-1种牌或自己本身搭配
long long int d[N];
int n;
int main()
{
  cin>>n;
  int i;
  for( i=1;;i++){
   d[i]=d[i-1]+i;
   
   if(d[i]>=n)break;
  }
cout<<i;
  // 请在此输入您的代码
  return 0;
}

4.时间显示

https://www.lanqiao.cn/problems/1452/learning/?subject_code=1&group_code=4&match_num=12&match_flow=1&origin=cup

#include <iostream>
using namespace std;
int main()
{
  long long int t;
  cin>>t;
  t/=1000;
  int h,m,s;
s=t%60;
m=t/60%60;
h=t/3600%24;
if(h<10)cout<<"0"<<h<<':';
else cout<<h<<':';
if(m<10)cout<<"0"<<m<<':';
else cout<<m<<':';
if(s<10)cout<<"0"<<s;
else cout<<s;
  // 请在此输入您的代码
  return 0;
}

G.砝码称重

#include<bits/stdc++.h>
using namespace std;
int w[110];
bool f[110][100010];
int n;
int m;
int res;
int main(){
  cin>>n;
  for(int i=1;i<=n;i++){cin>>w[i];
m+=w[i];
  }
  f[0][0]=true;
for(int i=1;i<=n;i++){
  for(int j=0;j<=m;j++){
    f[i][j]=f[i-1][j];
    f[i][j]|=f[i-1][abs(j-w[i])];
    if(j+w[i]<=m)f[i][j]|=f[i-1][j+w[i]];
  }
}
for(int i=1;i<=m;i++){
  if(f[n][i])res++;
}
cout<<res;
  return 0;
}

 算法分析:dp

状态表示:

f[i][j]表示取到第i个砝码可以称出来质量为j,称出来为true,否则为false

状态转移方程:思考从取到第i-1个怎么转移到取到第i个砝码且称出来质量为j

有三种转移情况->默认左物右码,所以右边+左边-

1.不取第i个物品

f[i-1][j]

2.第i个物品放在天平右边

f[i-1][j-w[i]]

3.第i个物品放在天平左端

f[i-1][j+w[i]]

三种转移情况取或因为只要任意一个满足就能转移到f[i][j]

二重循环:

外:遍历i从取1个到取到第n个

内:遍历j从0~m,m为能称出的最大质量

注意:

1.j+w[i]<=m因为遍历时j最大为m那么j+w[i]会超过m要防止

2.数组下标>=0所以j-w[i]取abs绝对值

3.注意dp初始化f[0][0]=true;//一个没取称到的质量为0

最后遍历所有可能质量1~m看f[n][i]是否为true即取到最后一个砝码能否达到当前质量。

H.杨辉三角

算法考点:

1.组合数-->会求Cr k

2.二分-->找到正确位置

分析:

易知:杨辉三角关于中间对称轴对称所以求最早出现位置只看左边

斜着看会发现从上到下递增,从左往右看中间最大,向外递减

所以最早位置的最佳选取是中间轴上,中间轴满足Ca 2a 从a=0开始

一定能找到一个位置因为Cn1=n最差位置

代码分析:

#include <iostream>
using namespace std;
typedef long long int ll;
int n;
//1.求组合数模版(担心太多所以用long long)
ll C(int a,int b){//求Cab
int i,j;
ll res=1;
for(i=a,j=1;j<=b;i--,j++){
res=(res*i)/j;
if(res>n)return res;//超过了就直接返回后面更大的就不要了,防止过多计算暴long long且超时!
}
return res;
}
bool check(int k){//判Crk
  //考二分-->精缩位置
  int l=2*k;//最好位置
  ll r=max(n,2*k);//最差位置,用max因为担心n太小,即这个k太大不合适

  //第一次的出现,最早出现二分模版
  while(l<r){
    ll mid=l+r>>1;
    if(C(mid,k)>=n)r=mid;
    else{
      l=mid+1;
    }
  }
  if(C(r,k)!=n)return false;
  cout<<((1+r)*r/2)+k+1;//Crk满足,那么这个元素前面有r行从左到自己有k列
  return true;
}

int main()
{
  cin>>n;
  for(int i=16;i>=0 ;i--){//从最下面最大的开始搞,可以提前结束循环
    if(check(i))break;
  }
  // 请在此输入您的代码
  return 0;
}
 
 

J.括号序列

考点:动态规划

思考:

1.增加左右括号方法相互独立,所以添加左括号有l种方案,添加右括号有r种,所以总方案数l*r

2.计算l的方法r方法类似(对称)

所以计算完l后

翻转字符串并且将左括号变右括号,右括号变左括号

 

#include<bits/stdc++.h>
using namespace std;
int n;//存输入字符串长度
typedef long long int ll;
const ll mod=1e9+7;
const int N=5010;
char str[N];//存括号
ll dp[N][N];//dp[i][j]表示取i个括号,左括号比右括号多几个
ll cal(){
  memset(dp,0,sizeof(dp));//初始化数组
  dp[0][0]=1;//取0个左括号比右括号多0个的方案数为1
  for(int i=1;i<=n;i++){
    //1.如果当前是左括号
    if(str[i]=='('){
      for(int j=1;j<=n;j++){
        dp[i][j]=dp[i-1][j-1];
      }
    }
    //2.如果当前是右括号
    else{
      //特判
      dp[i][0]=(dp[i-1][0]+dp[i-1][1])%mod;
      for(int j=1;j<=n;j++){
        dp[i][j]=(dp[i-1][j+1]+dp[i][j-1])%mod;
      }
    }
  }
  for(int i=0;i<=n;i++){
    if(dp[n][i])return dp[n][i];
  }
  return -1;
}
int main(){//1.字符串从1号位开始
scanf("%s",str+1);
n=strlen(str+1);
ll l=cal();
reverse(str+1,str+n+1);//1~n位翻转
for(int i=1;i<=n;i++){//左右括号反过来
  if(str[i]==')'){
    str[i]='(';
  }
  else{
    str[i]=')';
  }
}
ll r=cal();
cout<<(l*r)%mod;
return 0;
}
 
 
核心代码分析
动态规划:
for(int i=1;i<=n;i++){
    //1.如果当前是左括号
    if(str[i]=='('){
      for(int j=1;j<=n;j++){
        dp[i][j]=dp[i-1][j-1];
      }
    }
    //2.如果当前是右括号
分析:
1.特判j=0时,从i-1转移需要添加0个左括号dp[i-1][1],从i-1转移需要添加1个左括号dp[i-1][0]
dp[i][j]=(dp[i-1][0]+dp[i-1][1])%mod
2.j从1~n
从i-1转移需要添加0个左括号dp[i-1][j+1]
从i-1转移需要添加1个左括号dp[i-1][j]
...
dp[i][j]=dp[i-1][j+1]+dp[i-1][j]+dp[i-1][j-1]+...+dp[i-1][0];
dp[i][j-1]=dp[i-1][j]+dp[i-1][j-1]+...+dp[i-1][0]
所以状态转移方程dp[i][j]=(dp[i-1][j+1]+dp[i][j-1])%mod
 
    else{
      //特判
      dp[i][0]=(dp[i-1][0]+dp[i-1][1])%mod;
      for(int j=1;j<=n;j++){
        dp[i][j]=(dp[i-1][j+1]+dp[i][j-1])%mod;
      }
    }
  }
分析:
i从0~n看哪种情况非0
dp[n][i]所有括号选完
 
  for(int i=0;i<=n;i++){
    if(dp[n][i])return dp[n][i];
  }
  return -1;
}

I.双向排序

分析:

1.第一个有效操作是降序。

2.连续的降序或者是连续的升序只保留区间最长的那次操作

3.所以由2可以得降序升序交替进行

 4.有效的前缀区间长是严格减小的,有效的后缀区间长是严格减小的

代码分析:

#include<bits/stdc++.h>

//定义pair的两项

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;//第一维存操作数,第二维存处理区间端点,对应题目中p,q

const int N = 100010;

int n, m;
PII stk[N];//用栈存放操作
int ans[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);//连续降序q取最大
            while (top >= 2 && stk[top - 1].y <= q) top -= 2;//不连续降序交替出现,所以从栈中删除那些降序区间小于q的还有升序,所以top-2

             stk[ ++ top] = {0, q};//从栈下标为1开始存,将处理后的入栈
        }
        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;//左右端点l,r.k负责计数

//入录答案核心
    for (int i = 1; i <= top; i ++ )
    {
        if (stk[i].x == 0)
            while (r > stk[i].y && l <= r) ans[r -- ] = k -- ;
        else
            while (l < stk[i].y && l <= r) ans[l ++ ] = k -- ;
        if (l > r) break;
    }

//多余
    if (top % 2)
        while (l <= r) ans[l ++ ] = k -- ;
    else
        while (l <= r) ans[r -- ] = k -- ;

    for (int i = 1; i <= n; i ++ )
        printf("%d ", ans[i]);
    return 0;
}

完整代码

#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
const int N=100010;
typedef pair<int,int>PII;
PII st[N];//栈存放p,q
int ans[N];//存放答案
int main(){
int n,m;
int top=0;
cin>>n>>m;//长度为n,操作m次
while(m--){
  int p,q;
  cin>>p>>q;
  if(!p){//前缀降序
  while(top&& st[top].x==0){//连续的降序
  q=max(q ,st[top--].y);//栈顶弹出
  }
while(top>=2&&st[top-1].y<=q){//交替降序升序
top-=2;//top为升序,top-1为
}
//入栈
 st[++top]={0,q};
  }
  //升序,并且栈非空,因为第一个有效操作是降序
  else if(top){
while(top&& st[top].x==1){//连续升序
q=min(q, st[top--].y);
}
while(top>=2&& st[top-1].y>=q){//交替升降序,top为降序,top-1为升序
top-=2;
}
//入栈 
st[++top]={1,q};
  }
}
//入录答案
int k=n;//计数
int l=1;//左端点
int r=n;//右端点
for(int i=1;i<=top;i++){
  //从第一个操作开始,第一个有效操作前缀降序
  if (st[i].x==0){
  while(l<=r&&r> st[i].y){//前缀降序。间接的说明后面半段从右向左降序,r> st[i].y千万别取等。
ans[r--]=k--;
  }}
  else{
while(l<=r&& st[i].y>l){//后缀升序,间接说明左边半段从左到右降序,因为有效操作从降序开始。st[i].y>l请问别取等。
ans[l++]=k--;

}}
if(l>r)break;//定义循环的出口
}
//多余部分
if(top%2){
  //多个降序操作,因为操作的顺序降升降升...
  while(l<=r){ans[l++]=k--;}
}
else{
  //多个升序
  while(l<=r){
    ans[r--]=k--;
  }
}
//输出答案
for(int i=1;i<=n;i++){
  cout<<ans[i]<<" ";
}
return 0;
}

 

posted @ 2024-02-03 22:09  Annaprincess  阅读(29)  评论(0)    收藏  举报