昨天刷计蒜客,遇到这样一道题:
我把题目搬过来:
一个等差数列是一个能表示成 a,a+b,a+2b,...,a+nb(n=0,1,2,3,...)a,a+b,a+2b,...,a+nb(n=0,1,2,3,...)的数列。
在这个问题中 aa 是一个非负的整数,bb 是正整数。写一个程序来找出在双平方数集合(双平方数集合是所有能表示成 p2+q2p2+q2 的数的集合) SS 中长度为 nn 的等差数列。
输入格式
输入包括两行,第一行为 N(3≤N≤25)N(3≤N≤25) 要找的等差数列的长度。第二行是找到的双平方数 pp 和 qq 的上界 M(0≤p,q≤M)M(0≤p,q≤M)。
输出格式
输出一行或者多行,如果没有找到数列,输出NONE
。否则输出一个整数对a b
(这些行应该先按 bb 排序再按 a
a 排序)
题目读完,我的第一反应就是深搜,因为它本质上还是在所有可能性大组合中搜索符合条件的队列。
于是风风火火就写起来了:
//
// main.cpp
// ari_prgr
//
// Created by Horizon42 on 2018/3/20.
// Copyright © 2018年 Horizon42. All rights reserved.
//
#include <iostream>
#include <cstdio>
#include <vector>
#include <set>
#include <algorithm>
#include <cstdio>
using namespace std;
// 构造一个结构体存储a,b, 并重载它的 < 比较符号,以方便排序。
struct a_b{
int a;
int b;
friend bool operator<(a_b o, a_b t){
if(o.b!=t.b)
return o.b<t.b;
else
return o.a<t.a;
}
};
int MAXN= 0; //存储平方数的总数-1
int N = 0;
int prgr[26] = {0}; //存储搜索到的组合,长度为给定的数列长度。
vector<a_b> res; //存储结果
vector<int> squr_num; //存储所有符合条件(0<=p,g<=M)的平方数
//判断是否为等差数列的函数
bool is_ari(){
a_b tmp;
tmp.a = prgr[1];
tmp.b = prgr[2] - tmp.a;
for(int i=2;i<N;i++){
if(prgr[i+1]-prgr[i]!=tmp.b) return false;
}
res.push_back(tmp);
return true;
}
//深搜
void dfs(int cur, int cnt){
if(cur>MAXN||cnt>N) return;
prgr[cnt] = squr_num[cur];
if(cnt==N){
is_ari();
return;
}
for (int i =cur+1; i<=MAXN; ++i) {
dfs(i, cnt+1);
}
}
int main(){
int M=0;
cin>>N>>M;
set<int> tmp;
//生成平方数,为了防止出现重复的数字(如 0^2+5^2和3^2+4^2 都是25),用set来存。
//这是一种偷懒的方法,但也是一个蠢方法,因为它增大了时间复杂度
for(int i=0; i<=M; i++){
for(int j=i;j<=M; j++){
tmp.insert(i*i+j*j);
}
}
for(auto i = tmp.begin(); i!=tmp.end(); ++i){
squr_num.push_back(*i);
cout<<*i<<" ";
}
cout<<endl;
MAXN =(int)squr_num.size()-1;
//测试每个可能的a,寻找b
for (int i=0; i<=MAXN; ++i) {
dfs(i,1);
}
//将得出的结果排序
sort(res.begin(), res.end());
//输出结果
for(auto i = res.begin(); i!=res.end(); ++i){
cout<<(*i).a<<" "<<(*i).b<<endl;
}
return 0;
}
结果呢? 结果当然是超时了!!!
而且这已经是我绞尽脑汁减少递归次数提高效率的结果。折腾半天也就从通过一组编成了通过二组……
这种情况下,就只好认输,求助万能的百度。
没想到,正解竟然那么暴力。
先看代码:(代码是我了解解题思想之后自己写的)
//
// main.cpp
// ari_prgr_check 逆向思维的应用
//
// Created by Horizon42 on 2018/3/21.
// Copyright © 2018年 Horizon42. All rights reserved.
//
#include <iostream>
using namespace std;
bool checked[125001] = {0}; //标记某个数字是不是符合条件的平方数
int a[125001] = {0}; //存储所有可能的a,也就是所有平方数
int main(int argc, const char * argv[]) {
int N=0,M=0;
cin>>N>>M;
//初始化标记数组
for (int i=0; i<=M; ++i) {
for (int j=i; j<=M; ++j) {
checked[i*i+j*j]=true;
}
}
//初始化a
for (int i=0,j=0; i<=M*M*2; ++i) {
if (checked[i]) {
a[j++] = i;
}
}
bool NONE = true;//标记是否没有结果
int b=1;
int max_b = (M*M*2)/(N-1);
//b也就是公差由小到大开始遍历,有排序效果
for (; b<=max_b; ++b) {
//为当前的b公差搜索可能的a
for (int i=0; a[i]+(N-1)*b<=M*M*2; ++i) { // a[i]<=M*M*2这种写法 依然浪费了时间
//判断是不是等差数列
bool is_checked = true;
for (int n=1; n<N; ++n) { //从1开始 因为a[i] + 0*b 一定被标记数组记录为存在
if (!checked[a[i]+n*b]) {
is_checked = false;
break; //加快运行速度
}
}
if(is_checked){
cout<<a[i]<<" "<<b<<endl;
NONE = false;
}
}
}
if(NONE)
cout<<"NONE";
return 0;
}
代码注释得很清晰了,核心思想就是直接暴力搜索所有符合条件的组合。但它之所以效率高,主要有3点:
- 使用一个标记数组,避免出现重复的平方数的同时,也方便在搜索时判断等差数列成不成立
- 判断是否为等差数列时,用逆向思维,如果a[i]+n*b并没有在标记数组中被记录,则说明这个组合不符合要求。
- 从b也就是公差的角度开始搜索,节省了排序的时间。
可以说,受益良多。