目录
一、AC情况
全部四道题赛后补题AC
二、赛中概况
前两题做的很快,到第三题卡住,在最后把第四题暴力写上了。
前两题数组都开小了,第三题思路错误,第四题暴力拿了30分。
三、解题报告
问题一:平整(smooth)
情况:
赛后补题AC
题意:
给定一个序列,求有多少个数x满足:其减去序列中每个数的差的绝对值小于等于1。
比赛时很快就想好做出来了,但是数组开小了(开数组时少打了一个0),导致没有拿全分。
题解:
思维题,找到给出的序列中的最大最小值,分类讨论:
1.若最大值和最小值的差大于2,如1,2,3,4,5,6这组数,不难看出没有x满足题目条件,输出0;
2.若最大值和最小值的差等于2,如1,2,3,1,2,3这组数,满足题目条件的x只有1个,该序列中为2;
3.若最大值和最小值的差等于1,如1,2,1,1,2,1这组数,x有两个值能够满足题目条件,在该序列中x的两个取值分别为1和2;
4.若最大值和最小值的差等于0,即最大值等于最小值,就说明该序列中所有数都相等,那么x有3种可能满足题目条件。设该序列中所有数都是a,x的三种可能值分别是a-1、a、a+1。
这样我们就可以很轻松的做出这道题了。
正解代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int T,n,a[100050];
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
int maxx=-1,minn=1e9+7;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
maxx=max(maxx,a[i]);
minn=min(minn,a[i]);
}
if(maxx-minn>2){
printf("0\n");
}
else if(maxx-minn==2){
printf("1\n");
}
else if(maxx-minn==1){
printf("2\n");
}
else{
printf("3\n");
}
}
return 0;
}
问题二:字符串统计(statistic)
情况:
赛后补题AC
题意:
给定一个字符串序列并进行查询,每次查询输出[L,R]范围内以元音字母开头和结尾的字符串个数。
比赛时还是数组开小了导致没有AC。
题解:
不难想到,可以使用前缀和提前统计,对于每次查询输出前缀和数组统计的值即可。
正解代码:
#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
int n,m;
string s;
long long sum[100050];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
cin>>s;
int len=s.size();
len--;
if(s[0]=='a'||s[0]=='e'||s[0]=='i'||s[0]=='o'||s[0]=='u'){
if(s[len]=='a'||s[len]=='e'||s[len]=='i'||s[len]=='o'||s[len]=='u'){
sum[i]=sum[i-1]+1;
}
else{
sum[i]=sum[i-1];
}
}
else{
sum[i]=sum[i-1];
}
}
sum[n+1]=sum[n];
scanf("%d",&m);
int l,r;
while(m--){
scanf("%d%d",&l,&r);
printf("%d\n",sum[r+1]-sum[l]);
}
return 0;
}
问题三:从a到b(reach)
情况:
赛后补题AC
题意:
给定两个长度相同并且只由0和1组成的整数,进行不限次数的操作,每次操作选择两个不同的下标i和j,将a[i]修改为a[i]|a[j](按位或),将a[j]修改为a[i]^a[j](异或),判断能否通过这种操作将a转化为b。
比赛时想复杂了,用了暴搜,最后拿了零分。
题解:
这其实也是道思维题,若a和b本来就相等,可以直接输出,否则进行分析,不难发现,其实或和异或两种操作的特点导致只要原本数字a中有1,就不可能把1消掉,而如果原本数字a全是0没有1,也不可能将0转化为1。因此判断,只要a和b都含有1,那么a一定可以变成b,如果a和b中有一个全是0,那么a就不可能变成b。分析出这点,这题就非常简单了。
正解代码:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n;
string a,b; //这里使用string类型更方便判断,也不会消除前导0
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
cin>>a>>b;
if(a==b){
printf("OK\n");
continue;
}
bool flag1=0,flag2=0;
for(int i=0;i<n;i++){
if(a[i]=='1'){
flag1=1;
}
if(b[i]=='1'){
flag2=1;
}
}
if(flag1&&flag2){
printf("OK\n");
}
else{
printf("No\n");
}
}
return 0;
}
问题四:粉刷栅栏(color)
情况:
赛后补题AC
题意:
给定一个字符串,每个字符表示一种颜色,整个字符串表示整个栅栏,每次可以粉刷连续的多个栅栏。判断最少需要多少次能够将栅栏粉刷成给定的状态。
比赛时因为时间快没了而且没什么思路,于是打了个最简单的暴力,直接将字符串去重后输出长度,最终得了30分。
题解:
使用区间dp。
定义f数组,f[i][j]表示从i粉刷到j的最小次数。
推出状态转移方程:f[i][j]=min(f[i][j],min(f[i+1][j],f[i][j-1]));
枚举区间。
先枚举区间长度,再枚举区间左端点,根据这区间长度和左端点计算出右端点,再枚举区间中的分割点。
具体见正解代码。
正解代码:
#include<iostream>
#include<string>
#include<cstring>
using namespace std;
int f[55][55];
string s;
int main(){
memset(f,0x3f,sizeof(f)); //由于是找最小值,在一开始将f数组赋为极大值
cin>>s;
s=" "+s; //让下标从1开始
int len_s=s.size();
for(int i=1;i<=len_s;i++){
f[i][i]=1; //初始化,从i点到i点的粉刷次数一定是1
}
for(int len=2;len<=len_s;len++){ //枚举区间长度
for(int l=1;l+len-1<=len_s;l++){ //枚举左端点
int r=l+len-1; //根据这区间长度和左端点计算出右端点
if(s[l]==s[r]){ //左右端点颜色相等
f[l][r]=min(f[l+1][r],f[l][r-1]);
}
for(int k=l;k<r;k++){ //枚举分割点
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]);
}
}
}
cout<<f[1][len_s-1]; //由于len_s多记了一位,所以最后输出时要-1
return 0;
}
四、总结
这次比赛打的不是很好,主要前两题数组开小了导致丢了一百多分,第三题赛后来看其实很简单,赛中有可能做对,至少能拿更多分。还是要注意细节,不要把题想得太复杂,实在没思路就打暴力,说不定能拿更多分。最重要的还是心态,心态好,才能发挥出更好的水平。