逆向思维,以及如何严谨地设置搜索边界

昨天刷计蒜客,遇到这样一道题:

等差数列

我把题目搬过来:

一个等差数列是一个能表示成 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(3N25) 要找的等差数列的长度。第二行是找到的双平方数 pp 和 qq 的上界 M(0≤p,q≤M)M(0p,qM)

输出格式

输出一行或者多行,如果没有找到数列,输出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点:

  1. 使用一个标记数组,避免出现重复的平方数的同时,也方便在搜索时判断等差数列成不成立
  2. 判断是否为等差数列时,用逆向思维,如果a[i]+n*b并没有在标记数组中被记录,则说明这个组合不符合要求。
  3. 从b也就是公差的角度开始搜索,节省了排序的时间。

可以说,受益良多。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值