第十五届蓝桥杯大赛软件赛省赛C/C++ 大学 C 组(后四道)

第十五届蓝桥杯大赛软件赛省赛C/C++ 大学 C 组


前言

今天来做后四道,昨天没写完的

五、回文数组

刚开始的思路
回文数左右相等
那么我只要操作一边使其等于另一边即可
接下来就是如何操作的问题
那么我从左到右开始操作
当我在操作时按照案例
1 2 3 4
1-4=-3
2-3=-1
两者同负我就可以让两者同时+1
实现2 3 3 4
此时我就只需变更第一个就行
那么我可以设置一个数组来记录前一半和后一半的差值
判断符号是否相同来觉决定是否同时操作
以下代码

#include<iostream>
using namespace std;
int main()
{
    int n;
    cin>>n;
    long long a[100001];
    int b[100001];
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=1;i<=n/2;i++)
    {
        b[i]=a[i]-a[n-i+1];
    }
    int sum=0;
    for(int i=1;i<=n/2;i++)
    {
        while(b[i]!=0)
        {
            if(b[i]>0)
            {
                if(b[i+1]>0)
                {
                    b[i]--;
                    b[i+1]--;
                    sum++;
                }
                else
                {
                    b[i]--;
                    sum++;
                }
            }
            else if(b[i]<0)
            {
                if(b[i+1]<0)
                {
                    b[i]++;
                    b[i+1]++;
                    sum++;
                }
                else
                {
                    b[i]++;
                    sum++;
                }
            }
        }
     } 
    cout<<sum;
    return 0;
 }

上述为我第一次提交代码,炸时间了,然后我发现其实不用循环即可
将两个数绝对值小的即是需要共同操作的数目
剩余的再加给sum即可大大减少时间
代码如下

#include<iostream>
using namespace std;
int main()
{
	int n;
	cin>>n;
	long long a[100001];
	int b[100001];
	for(int i=1;i<=n;i++)
		cin>>a[i];
	for(int i=1;i<=n/2;i++)
	{
		b[i]=a[i]-a[n-i+1];
	}
	long long sum=0;
	for(int i=1;i<=n/2;i++)
	{
		if(b[i]>0)
		{
			if(b[i+1]>0&&b[i+1]>b[i])
			{
				b[i+1]-=b[i];
				sum+=b[i];
			}
			else if(b[i+1]>0&&b[i+1]<=b[i])
			{
				b[i+1]=0;
				sum+=b[i];
			}
			else 
			{
				sum+=b[i];
			}
		}
		else if(b[i]<0)
		{
			if(b[i+1]<0&&b[i+1]>b[i])
			{
				b[i+1]=0;
				sum-=b[i];
			}
			else if(b[i+1]<0&&b[i+1]<=b[i])
			{
				b[i+1]-=b[i];
				sum-=b[i];
			}
			else sum-=b[i];
		}
		else continue;
	 } 
	cout<<sum;
	return 0;
 } 

过了!

六、商品库存管理

很简单,却又炸时间的一道题
普通for循环去做时间复杂度为O(n²)会炸时间
如何把时间缩短为O(n)是这道题的重点
(1)利用前缀和把数组实现出来
循环实现时间复杂度依旧是O(n²),循环不可用
记录第一次操作的起点和终点x,y
a[x]+1,a[y+1]-1,在他的下一位减一那他在还原+1时就可以正好消掉
循环开始
a[i]+=a[i-1]
举例演示一下
6 1
3 5
上面是演示事例
0 0 1 0 0 -1 做好左右标记
0 0 1 1 1 0 从左到右a[i]+=a[i-1]
通过前缀和还原出数组
当多个一起时
6 2
1 4
3 5
上面是演示事例
1 0 1 0 -1 -1
1 1 2 2 1 0
通过前缀和来进行一次遍历还原数组

由题可知既然要求消去操作
那么就是使(x,y)区间内的数-1
然后找到0,给计数器++
那么当我的数组a(i)==1时就说明它在操作后会归零
我们接着思考
消去操作1,求0的个数
那就是求区域内的满足于条件的个数1的个数(1-1=0)
那我从左到右来记录第i个位置之前满足条件的个数
那么区域内满足条件的个数就可以用
区域末-区域头=区域满足条件的个数
由于我们知判断了1
那么原本为零的不能忘
一边循环遍历即可求出原本就为0的数的个数
以下代码展示

#include<iostream>
using namespace std;
int main()
{
	int n,m;
	cin>>n>>m;
	int a[n+10]={};
	int b[n+10]={};
	int x[n+10];
	int y[n+10];
	for(int i=1;i<=m;i++)
	{
		cin>>x[i]>>y[i];
		a[x[i]]++;
		a[y[i]+1]--;
	}
	for(int i=1;i<=n;i++)
	{
		a[i]+=a[i-1];
		//cout<<a[i]<<" ";
	}
	//cout<<endl;
	int zero=0;
	for(int i=1;i<=n;i++)
	if(a[i]==0)zero++;
	for(int i=1;i<=n;i++)
	{
		if(a[i]==1)b[i+1]+=b[i]+a[i];
		else b[i+1]+=b[i];
		//cout<<b[i]<<" ";
	}
	//cout<<endl;
	for(int i=1;i<=m;i++)
	cout<<b[y[i]+1]-b[x[i]]+zero<<endl;
	return 0; 
}

删掉注释可以进行演示两个关键数组的结果

七、挖矿

和第六题一样可以通过前缀和的方式来避免暴力超时
用两个数组来分别表示正的数和负的数,然后定义一个状态数组
将输入的位置上的状态改为1,表示可以挖矿
那么我就可以通过前缀和求出,如果不返回在当前位置上的最大挖矿数
接着开始思考
那如果返回,它最远能返回到哪里呢
一共只能走m步
走过去i步,走回来就还需要i步
那么当i×2<m时是不是说明如果返回可以走到另一个方向里面
此时呢,在另一个方向可以行走距离就为m-i×2
如此让两个位置上的最大挖矿数相加不就是,,当我走到i时折返能获得的最大挖矿数
明显当i×2>m时,折返无用,无法走到另一个方向
那么能挖到的矿就是,它从0一路走到i挖得的矿数
最后就是记录比大小
当然我们在进行运算的时候一直都忽略了起点,0有没有矿
所以在输入加一判定
如果输入的数==0
那么给定一个计数器=1
最后再加给最大值即可
代码如下

#include<iostream>
const int N=1e6+10;
int a[N]={0};
int b[N]={0};
using namespace std;
int main()
{
	int max2=0;
	int n,m,sum1=0;
	cin>>n>>m;
	int c;
	for(int i=1;i<=n;i++)
	{
		cin>>c;
		if(c>0)
		{
			a[c]++; 
		}
		else if(c<0)
		{
			b[abs(c)]++;
		}
		else sum1=1;
	}
	for(int i=1;i<=m;i++)
	{
		b[i]+=b[i-1];
		a[i]+=a[i-1];
	}
	for(int i = 1; i <= m; i++)
	{
    	int sum = a[i];
     	if(m-2*i>0)
     	{
         	sum+=b[m-2*i];
     	}
    	max2 = max(max2, sum);
     	sum=b[i];
     	if(m-2*i>0)
     	{
        	sum+=a[m-2*i];
     	}
    max2=max(max2,sum);
    }
	cout<<max2+sum1;
	return 0;
}

知识点 前缀和上一道题也有运用
其次再主函数中,int数组开不到1e6+10
但是如果定为在全局变量即可

八、回文字符串

字符串的输入不会点这里 函数输入还有这里scanf输入字符串
字符串长度计算
sizeof()包含空格
strlen()不含空格需要库#include<string.h>
代码如下

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

// 判断是否为回文字符串
bool pd(char *str, int start, int end) {
    while (start < end) {
        if (str[start] != str[end]) {
            return false;
        }
        start++;
        end--;
    }
    return true;
}
int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        char S[1000001];  // 最大长度为 10^6
        scanf("%s", S);
        int len = strlen(S);
        // 从左到右忽略前缀中无关字符 'l', 'q', 'b'
        int start = 0;
        while (start < len && (S[start] == 'l' || S[start] == 'q' || S[start] == 'b')) {
            start++;
        }
        // 从右到左忽略后缀中无关字符 'l', 'q', 'b'
        int end = len - 1;
        while (end >= 0 && (S[end] == 'l' || S[end] == 'q' || S[end] == 'b')) {
            end--;
        }
        // 判断剩余的核心部分是否是回文
        if (start >= end || pd(S, start, end)) {
            printf("Yes\n");
        } else {
            printf("No\n");
        }
    }
    return 0;
}

算是卡题的bug了,数据全过了,但是很明显的反例:llbmbpp
题的测试数据估计没设计好,这道题就过了吧=-=

总结

前缀和的理解和使用
int 开数组,全局变量能比在主函数开的更大
sizeof()包含空格
strlen()不含空格需要库#include<string.h>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值