题目描述
个数组组成序列
,求该序列最少可以划分成多少个区间,某个
区间中的数
到
从小到大排序后,一定是公差大于
的等差数列的子序列。
输入格式
第一行一个正整数 。
接下来一行包含 个正整数,第i个正整数为
。
输出格式
仅有一个正整数,表示最少可以被划分的区间数。
样例
样例输入 #1
7
1 5 11 2 6 4 7
样例输出 #1
3
样例输入 #2
8
4 2 6 8 5 3 1 7
样例输出 #2
2
提示
对于 的数据满足,
。
的数据满足,
。
的数据满足,
,
。
另有 的数据满足,
互不相同。
的数据满足,
,
。
这道题主要思路是贪心,也没想的那么难,我写的 ,感觉够快了。
思路
若干个数,如果可以属于公差大于 的等差数列的子序列。那么他们首先不能有相同的数,其次两两之间的差的最大公因数一定大于
。
我们利用贪心,把每个数尽可能地向前一段分,直到出现相同的数,或者两项之间的公差的最大公因数等于 为止,则需要另起一段新的划分,此时计数器加
。
由于 ,我们就用
记录数字是否重复出现,时间复杂度为
。
有个坑点就是:判断公差为 的时候如果写
只能拿
分,切记写上
。
完整代码
#include<bits/stdc++.h>
using namespace std;
map<int,int> so;
int a[100005];
int gcd(int a,int b){//二进制 Gcd
if(a==0) return b;
if(b==0) return a;
if(!(a&1)&&!(b&1)) return 2*gcd(a>>1,b>>1);
else if(!(a&1)) return gcd(a>>1,b);
else if(!(b&1)) return gcd(a,b>>1);
else return gcd(abs(a-b), min(a,b));
}
int main()
{
int n,cnt=0;
register int i,j;
scanf("%f",&n);
for(i=1;i<=n;i++) scanf("%d",&a[i]);
for(i=1;i<=n;i++){
so[a[i]]++;
//so记录每个数的出现次数
if(abs(a[i]-a[i+1])<=1){
//abs函数取绝对值,因为数组不是从小到大排序
//如果公差为 1就另起一段区间
cnt++;
so.clear();
continue;
}
i++,so[a[i]]++;
int d=abs(a[i]-a[i-1]);
for(j=i+1;j<=n;j++){
d=gcd(abs(a[j]-a[j-1]),d);
if(d==1||so[a[j]]>0) break;
//如果两两之间的公差为 1
//或是一个数多次出现
so[a[j]]++;
}
cnt++,i=j-1;
//i直接跳到下一段开始的位置
//j减去一是因为下一次循环开始时 i会加 1
so.clear();
//每次循环结束别忘了清空 map
}
printf("%d",cnt);
return 0;
}
防抄袭万岁
拓展知识点
二进制Gcd
二进制 只使用位移和加减法实现,因此运行效率较高,尤其在求大数的
时,效率明显提高,这是因为大数除法的实现很复杂。(就比如说本题的
)
二进制 计算当中共有
种情况:
1. 为偶数,则
。
2. 为奇数,则
。
3. 为奇数,
为偶数,则
。
4. 为
,则返回
。
优雅の代码
typedef unsigned long long ll;
ll gcd(ll a, ll b) {
if (a == 0)
return b;
if (b == 0)
return a;
if (!(a & 1) && !(b & 1))
return 2 * gcd(a >> 1, b >> 1);
else if (!(a & 1))
return gcd(a >> 1, b);
else if (!(b & 1))
return gcd(a, b >> 1);
else
return gcd(abs(a - b), min(a, b));
}