第十三届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组(个人见解,已完结)

一、九进制转十进制

题目

个人见解

手算或者进制转换都可。

AC代码

cpp 复制代码
#include <iostream>
using namespace std;
int main()
{
  cout<<1478;
  return 0;
}

二、顺子日期

题目

个人见解

直接挨个数即可,但是需要注意这里的 "顺子日期" 自带升序性质

AC代码

cpp 复制代码
#include <iostream>
using namespace std;
int main()
{
  cout<<14;
  return 0;
}
//0120
//0121
//0122
//0123
//0124
//0125
//0126
//0127
//0128
//0129
//1012
//1123
//1230
//1231

三、刷题统计

题目

个人见解

这里还是需要先用目标题数除以一个星期的题数剪枝一下,不然只能过 60% 的数据。

AC代码

cpp 复制代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

int main() 
{
    ll a,b,n;
    cin>>a>>b>>n;
    ll num=0;
    ll c=0;
    ll ans=n/(5*a+2*b)*7;
    n=n%(5*a+2*b);
    while(num<n)
    {
        if(c<=4)num+=a;
        else num+=b;
        c++;
        ans++;
    }
    cout<<ans;

    return 0;
}

四、修剪灌木

题目


个人见解

比较好写的思维题。

AC代码

cpp 复制代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

int main() 
{
    ll N;
    cin>>N;
    for(ll i=1;i<=N;i++)
    {
        ll maxv=max(i-1,N-i);
        cout<<2*maxv<<endl;
    }    

    return 0;
}

*五、X进制减法

题目


个人见解

本题不好理解的点在于题目给出的样例,即 X 进制数 321 是如何转换成十进制数 65 的。

其实题目描述的 X 进制只有在进位的时候有作用,某数位上的 1 代表的十进制取决于上一数位的 1 。

例如:最低位数为二进制,那么最低位数的 1 代表 1 ,当该位数的数字达到 2 就会进位;第二位数为十进制,但是是由最低位数进位来的,因此该位数的 1 代表 2 ,当该位数的数字达到 10 就会进位;第三位数为八进制,同理,该位数的 1 代表 20 ,当该位数达到 8 就会进位。

因此十进制数 65 是由 3 × 20 + 2 × 2 + 1 × 1 = 65 3\times20+2\times2+1\times1=65 3×20+2×2+1×1=65 得来的。

本题的贪心很好想,就是每个位数尽量取最小的进制。

接下来的普通解法就很容易想到了,直接借位即可,但是借位真的很麻烦。

这里推荐另一种解法---贡献法,因为只要求出最后的十进制数字,所以根本不需要关注数组元素相减可能出现负数的情况,只需要无脑算出每位的贡献即可。

最后!不要忘记输出的时候加上 mod ,否则可能会输出负数,导致不能 ac 。

cpp 复制代码
cout<<(ans+mod)%mod; 

AC代码(贡献法)

cpp 复制代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

ll mod=1e9+7;
ll a[100005];
ll b[100005];
ll ans=0;

int main() 
{
    ll N;
    cin>>N;
    ll ma,mb;
    cin>>ma;
    for(ll i=1;i<=ma;i++)
    {
        cin>>a[i];
    } 
    cin>>mb;
    for(ll i=1;i<=mb;i++)
    {
        cin>>b[i];
    }
    
    ll prev=1;
    while(mb)
    {
        ll w=max(a[ma],b[mb])+1;  //当前位数的进制
        w=max(w,2LL);
        ll cur=(a[ma]-b[mb])*prev%mod;  //当前数位的贡献
        prev=w*prev%mod;
        ans=(ans+cur)%mod;
        mb--,ma--;
    }
    while(ma)
    {
        ll w=max(2LL,a[ma]+1);
        ll cur=a[ma]*prev%mod;
        prev=w*prev%mod;
        ans=(ans+cur)%mod;
        ma--;    
    }   
    cout<<(ans+mod)%mod; 

    return 0;
}

*六、统计子矩阵

题目


个人见解

标准的滑动窗口题,这类题写的还是太少了,二维前缀和转成一维前缀和想了很久很久...

下面分别提供以列为前缀和&以行为前缀和两种出发点,但是本质都是一样的。

AC代码(滑动窗口)

  1. 将二维数组压缩成列前缀和数组。
cpp 复制代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

ll n, m, k;
ll a[505][505];
ll s[505][505]; // s[i][j] 表示第 j 列、前 i 行的垂直累加和
ll ans = 0;

int main() 
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin>>n>>m>>k;
    for(ll i=1;i<=n;i++)
    {
        for(ll j=1;j<=m;j++) 
        {
            cin>>a[i][j];
            // 计算列前缀和:当前格 + 同一列上一格的和
            s[i][j]=s[i-1][j]+a[i][j];
        }
    }

    for(ll i=1;i<=n;i++)  // 1. 固定上边界 i (从第几行开始)  
    {
        for(ll j=i;j<=n;j++)  // 2. 固定下边界 j (到第几行结束) 
        {
            // 3. 此时高度固定了,开始水平方向的滑动窗口
            ll L=1;
            ll sum=0; // 当前左右边界 [L, R] 之间的子矩阵总和
            
            for(ll R=1;R<=m;R++) 
            {
                sum+=(s[j][R]-s[i-1][R]);  // 加上第 R 列在 i~j 行之间的垂直片段

                // 如果窗口超出范围了,左边往右缩
                while(sum>k&&L<=R) 
                {
                    sum-=(s[j][L]-s[i-1][L]);
                    L++;
                }

                // 计数逻辑:
                // 如果 [L, R] 合法,那么以 R 为右边界的子矩阵有:
                // [L, R], [L+1, R], ..., [R, R],共 R-L+1 个
                ans+=(R-L+1);
            }
        }
    }

    cout<<ans<<endl;

    return 0;
}

2.将二维数组压缩成行前缀和数组。

cpp 复制代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

ll n,m,k;
ll a[505][505];
ll s[505][505];  //每一行的前缀和
ll ans=0;

int main() 
{
    cin>>n>>m>>k;
    for(ll i=1;i<=n;i++)
    {
        for(ll j=1;j<=m;j++)
        {
            cin>>a[i][j];
            s[i][j]+=s[i][j-1]+a[i][j];  //初始化每行前缀和
        }
    }
    for(ll i=1;i<=m;i++)  //固定一行中左起点
    {
        for(ll j=i;j<=m;j++)  //固定一行中右终点
        {
            ll up=1;
            ll sum=0;
            for(ll down=1;down<=n;down++)  //列举行数
            {
                sum+=s[down][j]-s[down][i-1];
                
                while(sum>k)
                {
                    sum-=s[up][j]-s[up][i-1];
                    up++;
                }
                
                ans+=down-up+1;
            }
        }
    }
    
    cout<<ans;

    return 0;
}

七、积木画

题目



个人见解

线性dp

记得取模!!!好不容易写出来结果一组都过不掉,取个模就 ac 了。

AC代码(线性dp)

cpp 复制代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

ll mod=1e9+7;
ll dp[10000005][3];  //0:只有第一行放了,1:只有第二行放了,2:两行全放了

int main() 
{
    ll n;
    cin>>n;
    dp[1][0]=0,dp[1][1]=0,dp[1][2]=1;
    dp[2][0]=1,dp[2][1]=1,dp[2][2]=2;
    for(ll i=3;i<=n;i++)
    {
        dp[i][0]=(dp[i-2][2]+dp[i-1][1])%mod;  
        dp[i][1]=(dp[i-2][2]+dp[i-1][0])%mod;
        dp[i][2]=(dp[i-1][2]+dp[i-2][2]+dp[i-1][0]+dp[i-1][1])%mod;
    }
    cout<<dp[n][2]%mod;
    
    return 0;
}

八、扫雷

题目


个人见解

无脑暴力即可。

AC代码

cpp 复制代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

ll n,m;
ll a[105][105];
ll ans[105][105];
ll dx[8]={0, 0,1,-1,1,-1,1,-1};
ll dy[8]={1,-1,0, 0,1,-1,-1,1};

int main() 
{
    cin>>n>>m;
    for(ll i=1;i<=n;i++)
    {
        for(ll j=1;j<=m;j++)
        {
            cin>>a[i][j];
        }
    }
    for(ll i=1;i<=n;i++)
    {
        for(ll j=1;j<=m;j++)
        {
            if(a[i][j]==1)
            {
                ans[i][j]=9;
                continue;
            }
            else
            {
                for(ll k=0;k<8;k++)
                {
                    ll x=i+dx[k],y=j+dy[k];
                    if(x<1||x>n||y<1||y>m)continue;
                    
                    if(a[x][y]==1)ans[i][j]++;
                }
            }
        }
    }
    for(ll i=1;i<=n;i++)
    {
        for(ll j=1;j<=m;j++)
        {
            cout<<ans[i][j]<<" ";
        }
        cout<<endl;
    }
    
    return 0;
}

*九、李白打酒加强版

题目



个人见解

  1. 记忆化搜索+DFS代替dp的一天。
    因为遇见花最多100次,我就把 cur 开到了 105 ,但是一定要记得判断 cur>m ,否则会数组越界!!!
  2. 害,线性dp还是写下吧。
    记得结果一定要输出 dp[n][m-1][1] (确保最后一次遇到的是花),如果输出 dp[n][m][0] 答案不对哦,会包含酒提前喝完,剩下遇见的全是店的情况。

AC代码(记忆化搜索+DFS)(线性dp)

  1. 记忆化搜索+DFS
cpp 复制代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

ll mod=1e9+7;
ll st[105][105][105];  //cur,n,m

ll dfs(ll cur,ll n,ll m)  //cur:酒余额,n:店次数,m:花次数  
{
    if(cur==1&&n==0&&m==1)  //到达终点
    {
        return 1;
    }
    if(cur==0||cur>m)  //无效结果
    {
        return 0;
    }
    if(st[cur][n][m]!=-1)return st[cur][n][m];  //该状态已经访问过
    
    ll res=0;
    if(n>0)res=(res+dfs(2*cur,n-1,m))%mod;
    if(m>0)res=(res+dfs(cur-1,n,m-1))%mod;
    st[cur][n][m]=res;
    
    return res;
}

int main() 
{
    memset(st,-1,sizeof(st));
    ll n,m;
    cin>>n>>m;
    
    cout<<dfs(2,n,m);
    
    return 0;
}
  1. 线性dp
cpp 复制代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

ll mod=1e9+7;
ll dp[105][105][105];  //遇见i次店次数,j次花,酒余额为k的方案次数  

int main() 
{
    ll n,m;
    cin>>n>>m;
    
    dp[0][0][2]=1;  //初始状态
    for(ll i=0;i<=n;i++)
    {
        for(ll j=0;j<=m;j++)
        {
            for(ll k=0;k<=m;k++)
            {
                if(i>=1&&k%2==0)dp[i][j][k]=dp[i-1][j][k/2];
                if(j>=1&&k>=1)dp[i][j][k]=(dp[i][j][k]+dp[i][j-1][k+1])%mod;
            }
        }
    }
    
    cout<<dp[n][m-1][1];
    
    return 0;
}

*十、砍竹子

题目



个人见解

真的很搞,怎么都只能过65%的数据,结果只需要把 sqrt 改成 sqrtl 就 ac 了,见识短浅了,第一次听说这个函数,或者利用 long double 强转也可以。

double为双精度浮点数,意味着它能精确表示的十进制有效数字大约只有 15 - 17 位,而题目给的 h 可以达到 10 18 10^{18} 1018 ,也就是 19 位,低位的数字直接被舍掉了。

cpp 复制代码
val=floor(sqrt((long double)(val/2+1)));

下面给大家提供了两种思路。

AC代码(链表+优先队列)(贡献法)

  1. 链表+优先队列
cpp 复制代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

const ll N=200005;
ll e[N],l[N],r[N],idx;
ll used[N];

//初始化
void init(){
    l[1]=0;
    r[0]=1;
    idx=2;
}
//在节点a的右边插入一个数x
void insert(int a,int x){
    e[idx]=x;
    l[idx]=a,r[idx]=r[a];
    l[r[a]]=idx,r[a]=idx++;
}
//删除节点a
void remove(int a){
    l[r[a]]=l[a];
    r[l[a]]=r[a];
}

int main() 
{
    ll n;
    cin>>n;
    
    init();
    auto cmp=[](pair<ll,ll> a,pair<ll,ll> b){  //first:元素,second:元素下标
      if(a.first==b.first)return a.second>b.second;
      return a.first<b.first;
    };
    priority_queue<pair<ll,ll>,vector<pair<ll,ll>>,decltype(cmp)> q(cmp);
    
    ll prev=0;  //上一个不相等的下标
    for(ll i=1;i<=n;i++)
    {
        ll h;
        cin>>h;
        if(h!=e[prev])
        {
            insert(prev,h);
            prev=idx-1;
            q.push({h,prev});
        }
    }
    
    ll cnt=0;
    while(!q.empty())
    {
        auto m=q.top();
        q.pop();
        ll val=m.first,pos=m.second;
        if(val==1)break;  //最大是1直接结束
        if(used[pos])continue;
        
        val=floor(sqrt((long double)(val/2+1)));
        cnt++;

        bool mleft=l[pos]!=0&&e[l[pos]]==val;  //判断是否和左边相等
        bool mright=r[pos]!=1&&e[r[pos]]==val; //判断是否和右边相等
        
        if(mleft&&mright)  //左右全相等,删自己和右边邻居
        {
            used[r[pos]]=1,remove(r[pos]);
            used[pos]=1,remove(pos);
            continue;
        }
        else if(mleft||mright)  //左右只有一个和自己相等
        {
            remove(pos);
            used[pos]=1;
            continue;
        }
        e[pos]=val;
        q.push({val,pos});
    }
    cout<<cnt;
    
    return 0;
}
  1. 贡献法求解后删掉多余的次数即可,依然要用 long double
cpp 复制代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;

const ll N=200005;
vector<ll> h[N];

int main() 
{
    ll n;
    cin>>n;
    ll cnt=0;
    for(ll i=1;i<=n;i++)
    {
        ll hv;
        cin>>hv;
        while(hv!=1)
        {
            h[i].push_back(hv);
            hv=floor(sqrt((long double)(hv/2+1)));
        }
        cnt+=h[i].size();
    }
    for(ll i=2;i<=n;i++)
    {
        if(h[i].empty()||h[i-1].empty())continue;
        auto p1=h[i-1].begin(),p2=h[i].begin();
        while(*p1!=*p2)
        {
            if(*p1>*p2)p1++;
            else if(*p1<*p2)p2++;
            if(p1==h[i-1].end()||p2==h[i].end())break;
        }
        if(p1!=h[i-1].end())cnt-=h[i].end()-p2;
    }
    cout<<cnt;
    
    return 0;
}
相关推荐
清空mega3 小时前
C++中关于数学的一些语法回忆(2)
开发语言·c++·算法
想唱rap3 小时前
线程池以及读写问题
服务器·数据库·c++·mysql·ubuntu
香蕉鼠片3 小时前
数据结构八股(一)
数据结构·算法
Mr_Xuhhh3 小时前
从理论到实践:深入理解算法的时间与空间复杂度
java·开发语言·算法
望眼欲穿的程序猿3 小时前
Vscode Clangd 无法索引 C++17 或者以上标准
java·c++·vscode
6Hzlia4 小时前
【Hot 100 刷题计划】 LeetCode 42. 接雨水 | C++ 动态规划与双指针题解
c++·算法·leetcode
Felven4 小时前
B. Promo
c语言
地平线开发者4 小时前
智能驾驶感知算法的演进
算法·自动驾驶
爱丽_4 小时前
B+ 树范围查询为什么快:页分裂/合并、索引设计与 SQL 写法优化
数据库·算法·哈希算法