题解 SP9385【MAIN111 - Strictly not a Prime】

· · 题解

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

  1. 「between two given integers A and B」,说的是 AB 之间,并没有保证 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_iO(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