题解 SP9385【MAIN111 - Strictly not a Prime】

posted on 2021-06-21 20:30:31 | under 题解 | source

这道题,有两个比较恶心的点:

  1. 「between two given integers A and B」,说的是 \(A\)\(B\) 之间,并没有保证 \(A<B\)
  2. 「subsequence」指的是子序列(不连续),不是子段(连续)。

首先,我们先想想怎么判断一个数是否是「Strictly not a Prime」,不难想到一种暴力方法:

把数字 a 转换成字符串 s
遍历 i 为 s 的所有子序列:
	如果 i 是质数:
    	返回 a 不是 Strictly not a Prime
返回 a 是 Strictly not a prime

判断 \(i\) 是不是质数很简单,写个线性筛 \(O(n=10^5)\) 筛一下,再 \(O(1)\) 判一下。

那怎么遍历 \(s\) 的所有子序列呢?我们可以写个 dfs,搜选或不选两种状态,搜到尽头,结果就是 \(s\) 的所有子序列。

接着,我们看到 \(1\leq T\leq 10^5\) 这个惊人的数据范围,显然不能每次都重新统计一次。考虑设 \(f_i\) 表示 \(i\) 是不是「Strictly not a Prime」,那么题目的询问实际上就是求 \(\sum^{b}_{i=a} f_i\)

Q:那怎么快速求 \(\sum^{b}_{i=a} f_i\) 呢?

A:使用前缀和。记 \(s_x=\sum^{x}_{i=1} f_i\),那么 \(\sum^{b}_{i=a} f_i=s_b-s_{a-1}\)

至此,本题得解。时间复杂度好像很离谱?其实很低,设 \(n=10^5\),一共也就是线性筛的 \(O(n)\),加上算 \(f_i\)\(O(n\times 2^{\log_{10}{10^5}})\),再加前缀和的 \(O(n)\),最后是回答询问的 \(O(T)\),一共就是 \(O(n+T)\),非常合理。

下面给出代码:

#include <sstream>
#include <iostream>
#include <algorithm>
using namespace std;
template<int N> class LineSiever{
    private:
        bool isp[N+10];
        int n,p[N+10];
    public:
        LineSiever():n(0){
            for(int i=1;i<=N;i++) isp[i]=1;
            isp[0]=isp[1]=0;
            for(int i=2;i<=N;i++){
                if(isp[i]) p[++n]=i;
                for(int j=1;j<=n&&1LL*i*p[j]<=N;j++){
                    if(isp[i*p[j]]=0,i%p[j]==0) break;
                }
            }
        }
        operator int(){return n;}
        bool operator()(int x){return isp[x];}
        int operator[](int x){return p[x];}
};
LineSiever<100010> p;
stringstream ss;
string chg(int a){
    string r;
    ss.clear(),ss<<a,ss>>r;
    return r;
}
int chg(string a){
    int r;
    ss.clear(),ss<<a,ss>>r;
    return r;
}
int n;
string dig;
bool dfs(int i,int tot){
    if(i==n) return p(tot);
    return dfs(i+1,tot*10-48+dig[i])||dfs(i+1,tot);
}
bool check(int a){
    dig=chg(a);
    n=dig.size();
    return !dfs(0,0);
}
int ans[100010];
int t,a,b;
int main(){
    for(int i=1;i<=1e5;i++) ans[i]=ans[i-1]+check(i);
    //for(int i=1;i<=1e5;i++) if(check(i)) cout<<i<<endl;
    cin>>t;
    while(t--){
        cin>>a>>b;
        if(a>b) swap(a,b);
        cout<<ans[b]-ans[a-1]<<endl;
    }
    return 0;
}

附一组易错样例:

input:

1
1 100000

output:

2568
posted @ 2022-11-10 09:25  caijianhong  阅读(20)  评论(0编辑  收藏  举报