本原串 (思维+快速幂+枚举约数)

本原串

 由0和1组成的串中,不能表示为由几个相同的较小的串连接成的串,称为本原串,有多少个长为n(n<=100000000)的本原串?
答案mod2008.
例如,100100不是本原串,因为他是由两个100组成,而1101是本原串。

Input
输入包括多个数据,每个数据一行,包括一个整数n,代表串的长度。
Output
对于每个测试数据,输出一行,代表有多少个符合要求本原串,答案mod2008.
Sample Input

1
2
3
4

Sample Output

2
2
6
12

分析:

对于一个长度为n的一个串来说,不考虑本原串,总的可能性是

2n 2 n

而我们要找本原串,可以逆向思维,用总的个数 2n 2 n 减去非本原串的个数

那么非本原串的个数怎么找呢?

根据题意我们知道非本原串就是由几个完全相同的串构成的

那么从长度上看,很显然:这几个相同的串的长度s那必然是长度n的约数

那么我们只需要减去长度为n的约数的子串的本原串的个数就可以了

你可能又有疑问,为什么之间去长度为n的约数的子串的本原串就可以了呢,那非本原串呢?

其实,对于长度为n的约数的子串,他的非本原串说明内部一定是由几个完全相同的这个子串长度的约数长度的更小子串构成,而这个更小子串的长度既然是子串长度的约数了,那必然也是长度n的约数,因此为避免重复,我们只要需要一一枚举除1和n本身的所有约数,然后减去这个长度的本原串个数就可以了。

递归求解

code:

#include 
using namespace std;
typedef long long ll;
const int mod = 2008;
map<int,ll> f;
ll q_pow(ll a,ll b){
    ll ans = 1LL;
    while(b){
        if(b & 1){
            ans = ans * a % mod;
        }
        b >>= 1;
        a = a * a % mod;
    }
    return ans % mod;;
}
ll cal(int n){
    if(f.count(n))
        return f[n];
    f[n] = (q_pow(2LL,(ll)n) - 2 + mod) % mod;
    for(int i = 2; i * i <= n; i++){
        if(n % i == 0){
            f[n] = (f[n] - cal(i) + mod) % mod;//找到一个因子
            if(i * i != n)
                f[n] = (f[n] - cal(n / i) + mod) % mod;//也就找到了另一个
        }
    }
    return f[n];
}
int main(){
    int n;
    f[0] = 0;
    f[1] = 2;
    f[2] = 2;
    while(~scanf("%d",&n)){
        if(n <= 2){
            printf("%lld\n",f[n]);
        }
        else{
            printf("%lld\n",cal(n));
        }
    }
    return 0;
}

你可能感兴趣的:(数论)