2020年蓝桥杯软件类省赛 C/C++ B组 解析

补一篇

A - 跑步训练

按秒记,跑步时每秒 \(hp-10\),休息时每秒 \(hp+5\),处理到某一时刻 \(hp=0\) 即可

答案 3880

void solve()
{
    int hp=10000;
    int rev=0,sec=0;
    int ans=0;
    while(hp>0)
    {
        ans++;
        if(rev==0)
            hp-=10;
        else
            hp+=5;
        sec++;
        if(sec==60)
        {
            sec=0;
            rev^=1;
        }
    }
    cout<<ans<<'\n';
}

B - 纪念日

处理出天数后直接乘 \(24*60\) 得出分钟数即可,注意闰年的处理

答案 52038720

void solve()
{
    int t[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
    int y=1921,m=7,d=23;
    int days=0;
    while(y!=2020||m!=7||d!=1)
    {
        days++;
        
        d++;
        if(d>t[m])
        {
            d=1;
            m++;
        }
        if(m>12)
        {
            m=1;
            y++;
            if(y%4==0&&y%100!=0||y%400==0)
                t[2]=29;
            else
                t[2]=28;
        }
    }
    cout<<days*24LL*60<<'\n';
}

C - 合并检测

不想推公式就用笨方法吧

对于每个 \(k=1\sim 100\) ,采用随机化或者大规模数据的方式多跑几次取平均值作为答案

下方代码采用的是大规模数据的方式,取 \(i\bmod 100=0\) 作为得病的人,跑出平均每人检测多少次

#define rep(i,a,b) for(int i=(a);i<=(b);i++)

double getAns(int k)
{
    int n=50000-50000%k;
    int sum=0,j=0,m=0;
    rep(i,1,n)
    {
        if(i%100==0)
            j++;
        if(++m==k)
        {
            if(j==0)
                sum++;
            else
                sum+=k+1;
            m=j=0;
        }
    }
    return 1.0*sum/n;
}

void solve()
{
    cout<<"k=1 ans=1.0"<<'\n';
    rep(k,2,100)
        cout<<"k="<<k<<" ans="<<getAns(k)<<'\n';
}

得出的结果如下

k=1 ans=1.0
k=2 ans=0.52
k=3 ans=0.363275
k=4 ans=0.29
k=5 ans=0.25
k=6 ans=0.226549
k=7 ans=0.212726
k=8 ans=0.205
k=9 ans=0.20094
k=10 ans=0.2
k=11 ans=0.2007
k=12 ans=0.203112
k=13 ans=0.206668
k=14 ans=0.211165
k=15 ans=0.216382
k=16 ans=0.2225
k=17 ans=0.228494
k=18 ans=0.235246
k=19 ans=0.242293
k=20 ans=0.25
...

易得 \(k=10\) 为最优解

答案 10

D - REPEAT 程序

观察输入的文件,发现每行数值不超过 \(9\),且 REPEAT 最多只有三层,因此答案不会太大

因此按行读入,取行前空格数 \(/4\) 作为层数,使用栈来存每层代表的 REPEAT 次数,借助栈来维护 A=A+... 语句的执行次数,直接按顺序处理即可

答案 241830

void solve()
{
    int A=0,B=1;
    string s;
    stack<int> sk;
    while(getline(cin,s))
    {
        int blank=0;
        while(s[0]==' ')
        {
            blank++;
            s=s.substr(1);
        }
        blank/=4;
        while(sk.size()>blank)
        {
            B/=sk.top();
            sk.pop();
        }
        if(s[0]=='A')
        {
            int n=s[8]-'0';
            A+=n*B;
        }
        else
        {
            int n=s[7]-'0';
            B*=n;
            sk.push(n);
        }
    }
    cout<<A<<'\n';
}

E - 矩阵

\(dp[i][j]\) 表示第一行放了 \(i\) 个数,第二行放了 \(j\) 个数的方案数

对于放的是什么数并不重要

因为同一行中右边的要比左边大,因此可以按从左到右的顺序处理

又因为同一列中下边的要比上边的大,因此第二行放的个数不能超过第一行,即任何时刻都需要满足 \(i\ge j\)

那么按照顺序放 \(1\sim 2020\) 这些数,每次只有 \(dp[i-1][j]\)(下一个数放第一行)与 \(dp[i][j-1]\)(下一个数放第二行)这两种状态可以转移至 \(dp[i][j]\)

答案 1340

#define rep(i,a,b) for(int i=(a);i<=(b);i++)

ll dp[1015][1015];
void solve()
{
    rep(i,1,1010)
    {
        dp[i][0]=1;
        rep(j,1,i)
        {
            dp[i][j]=dp[i][j-1];
            if(i<j)
            {
                dp[i][j]+=dp[i-1][j];
                dp[i][j]%=mod;
            }
        }
    }
    cout<<dp[1010][1010];
}

F - 整除序列

注意数据范围

void solve()
{
    ll n;
    cin>>n;
    while(n>1)
    {
        cout<<n<<' ';
        n>>=1;
    }
    cout<<n<<'\n';
}

G - 解码

void solve()
{
    string s;
    cin>>s;
    char pre;
    for(char c:s)
    {
        if(c>='0'&&c<='9')
        {
            int t=c-'0'-1;
            while(t--)
                cout<<pre;
        }
        else
        {
            cout<<c;
            pre=c;
        }
    }
}

H - 走方格

基础的DP,在 \(n\bmod 2+m\bmod 2=0\) 时不转移即可

#define rep(i,a,b) for(int i=(a);i<=(b);i++)

ll dp[35][35];
void solve()
{
    int n,m;
    cin>>n>>m;
    dp[1][1]=1;
    rep(i,1,n)
        rep(j,1,m)
        {
            if(i==1&&j==1)
                continue;
            if(i%2+j%2==0)
                continue;
            if(i-1>0)
                dp[i][j]+=dp[i-1][j];
            if(j-1>0)
                dp[i][j]+=dp[i][j-1];
        }
    cout<<dp[n][m]<<'\n';
}

I - 整数拼接

考虑将数字 \(b\) 拼接在数字 \(a\) 后这一操作对于结果 \(\bmod k\) 的值的影响

\(b\) 的数位数量为 \(len_b\),则答案也就是 \((a\times 10^{len_b}+b)\bmod k\)

由于输入的数值范围最高 \(10^9\),有 \(10\) 个数位,因此可以预处理每个数乘上 \(10\)\(1\sim 10\) 幂次后 \(\bmod k\) 的值,存储在 \(map\)

即记 \(map[i][j]\) 表示有多少个数 \(\times 10^i\) 之后 \(\bmod k=j\)

那么最后枚举一遍每个数 \(A_i\),即其数位数量为 \(L_i\),那也就是找有多少个数 \(\times 10^{L_i}\) 之后能与 \(A_i\) 相加为 \(K\) 的倍数,也就是找 \(\times 10^{L_i}\) 之后 \(\bmod K=(K-A_i\bmod K)\bmod K\) 的数的数量,即 \(map[L_i][(K-A_i\bmod K)\bmod K]\)

最后注意去除自身的影响

(如果是采用拼接后统计的方法做这题,注意两个 \(10^9\) 拼接得到的结果会大于 \(10^{19}\),需要使用 \(\text{unsigned long long}\))

#define rep(i,a,b) for(int i=(a);i<=(b);i++)

int n,K;
int A[100050];
map<int,int> mp[11];

int getLen(int a)
{
    int r=0;
    while(a)
    {
        a/=10;
        r++;
    }
    return r;
}
int pdo(int a,int b)
{
    a%=K;
    while(b--)
        a=a*10%K;
    return a;
}

void solve()
{
    cin>>n>>K;
    rep(i,1,n)
    {
        cin>>A[i];
        int tmp=A[i]%K;
        rep(j,1,10)
        {
            tmp=tmp*10%K;
            mp[j][tmp]++;
        }
    }
    ll ans=0;
    rep(i,1,n)
    {
        int len=getLen(A[i]);
        int rev=(K-A[i]%K)%K;
        int t=mp[len][rev];
        if(t>0&&pdo(A[i],len)==rev)
            t--;
        ans+=t;
    }
    cout<<ans<<'\n';
}

J - 网络分析

考虑并查集,每次向 \(p\) 发送信息相当于给当前 \(p\) 所在集合里的每个节点都发送一条信息,因此可以将每次发送的消息先记录在集合的根节点,最后再传递给集合的每个节点

但注意到可能一条信息发送之后,再将两个集合合并,但此前的消息是不能发送给另外一个集合内的节点的

因此考虑每次合并时往图中加点,用新节点来表示待合并的两个集合的新根,而这个新根则向原先两个集合的根连一条有向边

那么权值的传递便有了方向,每个新节点上的权值只对子树内的根节点会有影响,dfs将值传递下去即可

对于样例:

#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back

int gp[111111];
vector<int> G[111111];
int ans[111111];
bool vis[111111];

int fnd(int p)
{
    return p==gp[p]?p:(gp[p]=fnd(gp[p]));
}

void dfs(int u)
{
    for(int &v:G[u])
    {
        if(!vis[v])
        {
            vis[v]=true;
            ans[v]+=ans[u];
            dfs(v);
        }
    }
}

void solve()
{
    int n;
    cin>>n;
    rep(i,1,n)
        gp[i]=i;
    int tot=n;
    
    int m;
    cin>>m;
    while(m--)
    {
        int op;
        cin>>op;
        if(op==1)
        {
            int a,b;
            cin>>a>>b;
            a=fnd(a);
            b=fnd(b);
            if(a!=b)
            {
                tot++; // 新的根节点
                gp[tot]=tot;
                G[tot].pb(a);
                G[tot].pb(b);
                gp[a]=tot;
                gp[b]=tot;
            }
        }
        else
        {
            int p,t;
            cin>>p>>t;
            p=fnd(p);
            ans[p]+=t; // 往根节点上做标记
        }
    }
    per(i,tot,1) // 一定要倒着来,让新节点先搜
        if(!vis[i])
        {
            vis[i]=true;
            dfs(i);
        }
    rep(i,1,n)
        cout<<ans[i]<<(i==n?'\n':' ');
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值