第十五届蓝桥杯大赛软件赛省赛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>