题意
Input
Output
输入样例
2
5
1 2 3 4 5
5
1 2 3 4 5
输出样例
NO
NO
提示
分析
第一题都比较简单,这道题的题意和解决办法都很简单,但是正是因为太简单,所以写很快就提交的后果往往是WA。
- 序列的特征
根据题意可知:
一个序列中的每个数都可以通过加上k、减去k或保持不变这三种操作中的任意一种变成同一个数。
反之理解为,一个数列中只可能有以下三种情况:
- 存在三个互不相同的数:B + k 、B 、B - K
- 存在任意两个互不相同的数:A 、B
- 只存在一个数:B
显然,这三种情况都符合题目所给序列要求。
- 代码实现
在一开始看完题的时候大家几乎都能想到可以通过数据中出现不同数的个数来判断当前序列是否符合要求。
我最开始想到了两种实现方法:
- 依次统计序列中出现数的个数
- 用二分判断除去最大值最小值后的序列中剩下的数
我在模测时选择的是第二种方法,不过之后发现一个点都没过,我一度怀疑是自己方法有问题。最后复盘我将两种方法都写出来并完善后AC,在这里都分析一下。
1. 依次统计输入序列中出现的不同数的个数
在每个序列输入的过程中就可以直接进行判断。
- 在输入过程中
统计出现的不同数的个数。将所有第一次出现的数字进行标记,出现数个数增加1,并将其存入数组中;已经出现过的数字则不做操作。每输入一个数字就对当前出现数个数进行判断,若大于3,则直接标记该序列不符合要求,其后数据全都不做操作。 - 输入结束后
若出现数个数小于等于2,则直接标记为符合要求。
若出现个数等于3,则将出现的三个数进行排序,计算最大值、最小值与中间值的差。若相等,则符合要求;否则不符合要求。
2. 二分
如果一个序列符合要求,则当去掉该序列中两个最值的中间值之后,序列中只会剩下最值。
抓住这个特征,利用二分实现快速判定:
二分法👉[week4]四个数列——二分法(巧用)
- 将输入序列进行升序排列,首尾分别为序列中的最值
- 若最值相等或相差为2,则直接判断为符合要求;
- 若最值之间的的和为奇数,说明不存在中间值,则不进行删除,直接进入二分;否则删除序列中所有的中间值,进入二分;
- 二分剩下序列,若当前中间数等于最小值,则其左边一定都为最小值,所以右分继续查看更大的数;若当前中间数等于最大值,则同理,继续左分查看更小的数;一旦出现不是最值的数,直接判定为不符合要求。
【小tip:
1.为什么最值相等或相差为2就一定符合要求?
1)最值相等说明序列中只有一个数,一定符合要求
2)最值相差最大为2时,由于序列中的数都为正整数,因此序列在此前提下最多有三个数时,也一定只会是连续的三个数,所以一定符合要求。
2.为什么最值之和为奇数就不存在中间值?
在整型的除法运算中,计算结果不为整数时一律向下取整,即直接省略小数部分,所以最值和为奇数时,可能得到的中间值恰好存在于序列中,但是和为奇数的两个整数一定不存在整数中间值使得其到两点的距离相等。
因此若序列中仅存在3个数,且中间值恰好等于最值相加所得奇数除以2的结果,在删除中间值后,二分的结果判断就会为符合要求,但实际上并不符合。如,[2,4,7]。
】
-
出现的问题
-
忽略了二分法中的特殊情况
此处所提到的特殊情况就是指最值之和为奇数。如果不紧密贴合计算机思维,在快速思考再转换为计算机方法的过程中确实很容易漏掉这样关乎计算机运算特性所带来的小问题。但是这样的问题运气不好就会让你一个点都过不了。
- 何时清空容器?
这是一个再小不过的问题了,我们在平时练习做题的过程中会屡次犯错,所以大家在模测的时候也基本都会注意。
但是这次就不是忘记清空的问题了,而是忽略了前面的一些条件判定会跳过清空操作的问题。
在我模测写二分法代码以及之后修改,包括写另外一种方法的过程中,我都屡次犯了这个错误,一直处于一个点都无法AC的情况,却全然没有发现为什么。
若清空操作位于循环体的末尾,则当在一次循环按序执行的过程中,如果在循环体中增加了判定特殊情况的条件,并在其结构体内直接附上break或continue等直接跳过循环体中剩余代码的操作,可能会出现下一次循环时容器并未清空的情况。
💡解决办法
- 在需要初始化或是清空容器的循环代码中,在循环体内部添加任何特殊判定时,都应检查是否存在上述风险。
- 🌟推荐方法:将所有清空或初始化操作都放在循环体的最前面,这样保证每一次进入循环时,容器都处于可用状态。
总结
- 原本以为稳拿200,结果没想到稳栽第一题【懵】不过再一次提醒了我在测试过程中什么情况都有可能发生,模测的意义就在于不断完善自己做题和调试的能力
- 不能因为题目简单就过于自信,不进行调试就直接提交,这样就算为后面争取了足够时间,但是简单的题一分没拿到也等于没啥用😭
代码
方法1
//
// main.cpp
// lab1
//
//
#include <iostream>
#include <vector>
#include <algorithm>
#include <deque>
#include <map>
using namespace std;
vector<long long> a;
map<long long,bool> judge;
int main()
{
ios::sync_with_stdio(false);
int t = 0,n = 0;
long long number = 0,sum = 0;
cin>>t;
while( t-- )
{
bool judge2 = false;
cin>>n;
for( int i = 0 ; i < n ; i++ )
{
cin>>number;
if( !judge[number] ) //如果当前数字未出现过
{
// cout<<number<<" ! "<<endl;
sum++; //出现数字个数+1
judge[number] = true; //标记为已出现
a.push_back(number); //并将该数字放入数组中
}
else if( judge[number] || judge2 ) //如果该数字已经出现或是已经不符合要求,则跳过继续
continue;
if( sum > 3 ) //若出现数字总数大于3,则已经不符合要求
judge2 = true;
}
if( judge2 ) //若已经不符合要求,则输出并跳过
cout<<"NO"<<endl;
else
{
if( sum < 3 ) //若出现个数小于3,则一定符合要求
cout<<"YES"<<endl;
else if ( sum == 3 ) //若出现个数等于3
{
sort(a.begin(), a.end()); //给三个数字排序
if( a[1] - a[0] == a[2] - a[1] ) //若三个数字之间的差值相等,则符合要求
cout<<"YES"<<endl;
else //否则不符合
cout<<"NO"<<endl;
}
}
a.clear(); //清空map和vector
judge.clear();
sum = 0;
}
return 0;
}
方法2
//
// main.cpp
// lab1
//
//
#include <iostream>
#include <vector>
#include <algorithm>
#define INF 1e16
using namespace std;
vector<long long> a;
int main()
{
ios::sync_with_stdio(false);
int t = 0,n = 0;
long long number = 0,max = 0,min = INF,dif = 0;
cin>>t;
while( t-- )
{
bool judge = true; //用于判断
cin>>n;
for( int i = 0 ; i < n ; i++ ) //输入所有数
{
cin>>number;
a.push_back(number);
}
sort(a.begin(), a.end()); //升序排列
min = a[0]; //取最大最小值
max = a[n-1];
//错误点1!最开始写的时候直接在此处continue,但是清空容器的操作在每次循环的最后,因此跳过了清空
//所以以后最好把清空和初始化操作都放在一次循环的最开始,这样不论之后的特殊情况是否会continue,都不会错过这些基本操作
if( min == max || max - min <= 2 ) //若最大最小值相等,则一定能满足
cout<<"YES"<<endl;
else
{
//错误点2!!
//若差值为奇数,那么就可能会错误去除不是中值的数,使得最后判断出错
//因为整数型的运算是上取整的(比如 2 4 7这一组数据)
if( (max + min) % 2 == 0 ) //若最值之差为偶数,则删除中值
{
dif = (max + min) >> 1; //得到中值
a.erase(remove(a.begin(), a.end(), dif), a.end()); //删除所有等于中值的数
}
//则若该数列符合要求,则剩下元素应都为最大值或最小值
long long left = 0,right = a.size() - 1;
// cout<<min<<" "<<max<<" ^^ "<<endl;
while( left <= right ) //进行二分
{
long long mid = (left + right) >> 1;
// cout<<a[mid]<<" ** "<<endl;
if( a[mid] == min ) //如果当前中间值等于最小值,则搜索更大的部分(右分)
left = mid + 1;
else if( a[mid] == max ) //如果当前中间值等于最大值,则搜索更小的部分(左分)
right = mid - 1;
else //否则说明存在第三种数,不符合要求
{
judge = false; //标记失败
break;
}
}
if( !judge )
cout<<"NO"<<endl;
else
cout<<"YES"<<endl;
}
a.clear();
}
return 0;
}