减小时间复杂度的个人见解

L - Chance

After Noura’s brother, Tamim, finished his junior training, coach Fegla was impressed with his progress. He told Noura that if Tamim would solve the following problem in SCPC2015 and yet does not qualify to the ACPC2015, he will give him a chance to participate unofficially in it.

The problem goes as follows:

Given L and R, how many integers between them have a prime number of ones in their binary representation?

Can you help Tamim participate in the ACPC2015?

Input
The first line of input contains an integer T (1 ≤ T ≤ 1e5), the number of test cases.

Each test case will consist of two space - separated integers: L and R (0 ≤ L ≤ R ≤ 1e5).

Output
For each test case, print the number of integers between L and R that have a prime number of ones in their binary representation.

题目大意是给你一个l和r,要你找出l和r中间的数(化为二进制之后一的数量)是素数的数有几个,值得注意的是一的数量不会太多,所以只开到30就好了

但是我想借着这个题讲讲时间复杂度的缩短办法,比如我第一次的代码是这个样子的

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

bool f[30];

int main()
{
    memset(f, 0, sizeof(f));
    f[2]=f[3]=f[5]=f[7]=f[11]=f[13]=f[17]=f[19]=f[23]=f[29]=1;
    int t, l, r, n, m, s, sum;
    scanf("%d", &t);
    while(t--)
    {
        sum=0;
        scanf("%d %d", &l, &r);
        for(n=l;n<=r;n++)
        {
            m=n;
            s=0;
            while(m)
            {
                if(m%2==1)
                    s++;
                m/=2;
            }
            if(f[s])
                sum++;
        }
        printf("%d\n", sum);
    }
    return 0;
}

这里我没有用任何的减小办法,就是暴力地算一遍
但是,我没有发现这道题的T(循环次数)竟然达到了1e5次,在这么多次的摧残下每次都重新算一遍肯定是不大行的,所以比较好的办法是把所有的数值先算出来并且储存起来,以备后用,比如下面的代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
const int MAXN=10010;
using namespace std;

bool f[30];
bool a[MAXN];
int t, l, r, n, m, s, sum;
void fff()
{
    memset(f, 0, sizeof(f));
    f[2]=f[3]=f[5]=f[7]=f[11]=f[13]=f[17]=f[19]=f[23]=f[29]=1;
    memset(a, 0, sizeof(a));
    for(n=0; n<=MAXN; n++)
    {
        m=n;
        s=0;
        while(m)
        {
            if(m%2==1)
                s++;
            m/=2;
        }
        if(f[s])
            a[n]=1;
    }
}
int main()
{
    fff();
    scanf("%d", &t);
    while(t--)
    {
        sum=0;
        scanf("%d %d", &l, &r);
        for(int i=l;i<=r;i++)
        {
            if(a[i])
                sum++;
        }
        printf("%d\n", sum);
    }
    return 0;
}

在这里我定义了一个bool型的数组,用来标记从0到1e5(lr的最大值)的书是否满足要求(二进制的1加起来是素数),并且把预处理放进一个函数中,这样确实可以省时间,但是在主函数中还是要统计从l到r的满足的数量。

我以为这样就已经很低了,但是细想就会知道,若是l和r差距太大,再把这儿个数乘以十的五次方,那么这个就和改进前差不了多少了。采用了与处理的形式,却和之前钱差不多!?这肯定是不能容忍的。所以只能再次进行改进,如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
const int MAXN=100100;
using namespace std;

bool f[30];
int a[MAXN];
int t, l, r, n, m, s, sum;
void fff()
{
    memset(f, 0, sizeof(f));
    f[2]=f[3]=f[5]=f[7]=f[11]=f[13]=f[17]=f[19]=f[23]=f[29]=1;
    memset(a, 0, sizeof(a));
    a[0]=0;
    for(n=0; n<=MAXN; n++)
    {
        m=n;
        s=0;
        while(m)
        {
            if(m%2==1)
                s++;
            m/=2;
        }
        if(f[s])
            a[n]=a[n-1]+1;
        else
            a[n]=a[n-1];
    }
}
int main()
{
    fff();
    scanf("%d", &t);
    while(t--)
    {
        sum=0;
        scanf("%d %d", &l, &r);
//        for(int i=l;i<=r;i++)
//        {
//            if(a[i])
//                sum++;
//        }
        printf("%d\n", a[r]-a[l-1]);
    }
    return 0;
}

可以看到这时我把bool型的数组改成了int型的数组,用来累计从零开始的符合条件的个数,最后在主函数中只需要调用一下a[l]和a[r]两个值就好了,这就使得预处理有了意义。

总结:
1.读题是关键,比如本题预先看出处理次数如此大,就不会绕弯子了,当运 算次数比较大的时候,可以考虑先进行预处理,循环时只要输出结果就好了。

2.学会用动态规划的方法进行时间复杂度的优化,比如这里就用了int数组a储存,当然不要忘了寻找状态转移方程,这是个重点也是个难点。

3看题目的特点,或者说边界条件。比如我这里用了一个f数组储存素数的数量,因为本题不会太大(因为是位数的累计,所以就算二进制也大不到哪去),这样就不会因为每次都要判断素数而增大时间复杂度了。其他找素数的办法还有素数筛等,(当然素数筛也是一种预处理),都可以降低时间复杂度。
(附赠自己写的素数筛

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值