[Luogu P2480] [BZOJ 1951] [SDOI2010]古代猪文

版权声明:欢迎转载蒟蒻博客,但请注明出处:blog.csdn.net/lpa20020220 https://blog.csdn.net/LPA20020220/article/details/82497526

洛谷传送门

BZOJ传送门

题目背景

“在那山的那边海的那边有一群小肥猪。他们活泼又聪明,他们调皮又灵敏。他们自由自在生活在那绿色的大草坪,他们善良勇敢相互都关心……”

——选自猪王国民歌

很久很久以前,在山的那边海的那边的某片风水宝地曾经存在过一个猪王国。猪王国地理位置偏僻,实施的是适应当时社会的自给自足的庄园经济,很少与外界联系,商贸活动就更少了。因此也很少有其他动物知道这样一个王国。

猪王国虽然不大,但是土地肥沃,屋舍俨然。如果一定要拿什么与之相比的话,那就只能是东晋陶渊明笔下的大家想象中的桃花源了。猪王勤政爱民,猪民安居乐业,邻里和睦相处,国家秩序井然,经济欣欣向荣,社会和谐稳定。和谐的社会带给猪民们对工作火红的热情和对未来的粉色的憧憬。

小猪iPig是猪王国的一个很普通的公民。小猪今年10岁了,在大肥猪学校上小学三年级。和大多数猪一样,他不是很聪明,因此经常遇到很多或者稀奇古怪或者旁人看来轻而易举的事情令他大伤脑筋。小猪后来参加了全猪信息学奥林匹克竞赛(Pig Olympiad in Informatics, POI),取得了不错的名次,最终保送进入了猪王国大学(Pig Kingdom University, PKU)深造。

现在的小猪已经能用计算机解决简单的问题了,比如能用P++语言编写程序计算出A + B的值。这个“成就”已经成为了他津津乐道的话题。当然,不明真相的同学们也开始对他刮目相看啦~

小猪的故事就将从此展开,伴随大家两天时间,希望大家能够喜欢小猪。

题目描述

猪王国的文明源远流长,博大精深。

iPig在大肥猪学校图书馆中查阅资料,得知远古时期猪文文字总个数为 N 。当然,一种语言如果字数很多,字典也相应会很大。当时的猪王国国王考虑到如果修一本字典,规模有可能远远超过康熙字典,花费的猪力、物力将难以估量。故考虑再三没有进行这一项劳猪伤财之举。当然,猪王国的文字后来随着历史变迁逐渐进行了简化,去掉了一些不常用的字。

iPig打算研究古时某个朝代的猪文文字。根据相关文献记载,那个朝代流传的猪文文字恰好为远古时期的k分之一,其中k是N的一个正约数(可以是 1 N )。不过具体是哪 k 分之一,以及 k 是多少,由于历史过于久远,已经无从考证了。

iPig觉得只要符合文献,每一种能整除 N k 都是有可能的。他打算考虑到所有可能的 k 。显然当 k 等于某个定值时,该朝的猪文文字个数为 N / k 。然而从N个文字中保留下 N / k 个的情况也是相当多的。iPig预计,如果所有可能的 k 的所有情况数加起来为 P 的话,那么他研究古代文字的代价将会是 G P 次方。

现在他想知道猪王国研究古代文字的代价是多少。由于iPig觉得这个数字可能是天文数字,所以你只需要告诉他答案除以 999911659 的余数就可以了。

输入输出格式

输入格式:

输入文件ancient.in有且仅有一行:两个数 N G ,用一个空格分开。

输出格式:

输出文件ancient.out有且仅有一行:一个数,表示答案除以999911659的余数。

输入输出样例

输入样例#1:

4 2

输出样例#1:

2048

说明

数据规模

10%的数据中, 1 N 50

20%的数据中, 1 N 1000

40%的数据中, 1 N 100000

100%的数据中, 1 G 1000000000 1 N 1000000000

解题分析

显然, 题目要我们求:

G d | N ( N d )   m o d   999911659

根据初步计算, 999911659 是一个质数, 根据费马小定理可得
G d | N ( N d )   m o d   999911659 = G d | N ( N d )   m o d   999911658   m o d   999911659

那么关键在于求出这个玩意:
d | N ( N d ) m o d   999911658

很讨厌的是, 999911658 并不是一个质数, 其等于 2 × 3 × 4679 × 35617

考虑在 N 时间内枚举 d , 如果我们知道以下四个方程的解:

( N d )   m o d   2 x 1 ( N d )   m o d   3 x 2 ( N d )   m o d   4679 x 3 ( N d )   m o d   35617 x 4

因为模数两两互质, 那么就可以用 C R T 合并得到最终的解。

现在我们考虑如何快速求 ( N d )   m o d   p k 的值。

( N d ) = N ! d ! × ( N d ) ! , 而下面一团太大了, 无法直接求出, 我们需要求出其逆元。

但是在下面不与 p k 互质的情况下, 我们是无法求出逆元的, 所以干脆把上下的 p 都提出来。 大概长这样:

N ! p x 1 d ! p x 2 × ( N d ) ! p x 3 × p x 1 x 2 x 3   m o d   p k

这下分式下面就可以求逆元了。

如何提出 p ? 以下以 p = 3 , k = 2 , N = 20 为例。

N ! = ( 1 × 2 × 3 × 4 × 5 × 6 ) × 3 6 × ( 1 × 2 × 4 × 5 × 7 × 8 ) × ( 10 × 11 × 13 × 14 × 16 × 17 ) × 19 × 20

前一节是 N 以内含有 p 因子的数提出来之后的结果, 可以递归处理。

后面两节是 p k 之内的含有循环节的部分, 在 m o d   p k 意义下相等, 可以 O ( p k ) 预处理出来。

最后一节是零散的部分, 直接暴力即可。

至于求 N ! p 因子的数量, 直接这样求:
x 1 = N p + N p 2 + N p 3 + + N p n 。 在 l o g ( N ) 时间内可以得到。

最后还有一点, 当 G = k × 999911659 时直接输出0, 因为快速幂会返回1…

代码如下:

#include <cstdio>
#include <cctype>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define W while
#define ll long long
#define MX 100050
#define MOD 999911659
template <class T>
IN void in(T &x)
{
    x = 0; R char c = gc;
    for (; !isdigit(c); c = gc);
    for (;  isdigit(c); c = gc)
    x = (x << 1) + (x << 3) + c - 48;
}
ll pre[MX], fact[4] = {2, 3, 4679, 35617}, fac[MX], N, G;
int pcnt;
IN ll fpow(ll x, ll tim, ll mod)
{
    ll ret = 1;
    W (tim)
    {
        if(tim & 1) ret = ret * x % mod;
        x = x * x % mod; tim >>= 1;
    }
    return ret;
}
void exgcd(ll a, ll b, ll &x, ll &y)
{
    if(!b) return x = 1, y = 0, void();
    exgcd(b, a % b, x, y);
    ll buf = x; x = y, y = buf - a / b * y;
}
IN ll getinv(ll tar, ll mod)
{
    ll x, y;
    exgcd(tar, mod, x, y);
    return (x % mod + mod) % mod;
}
ll mul(ll n, ll p, ll pk)
{
    if(n <= 1) return 1; ll ret = 1;
    if(n >= pk) ret = fpow(pre[pk - 1], n / pk, pk);
    ret = ret * pre[n % pk] % pk;
    ret = ret * mul(n / p, p, pk) % pk;
    return ret;
}
IN ll C(ll n, ll m, ll p, ll pk)
{
    if(n < m) return 0;
    pre[0] = pre[1] = 1;
    for (R int i = 2; i < pk; ++i)
    {
        pre[i] = pre[i - 1];
        if(i % p) pre[i] = pre[i] * i % pk;
    }
    ll up1 = mul(n, p, pk), down1 = getinv(mul(m, p, pk), pk), down2 = getinv(mul(n - m, p, pk), pk);
    ll tim = 0;
    for (ll i = n / p; i; i /= p) tim += i;
    for (ll i = m / p; i; i /= p) tim -= i;
    for (ll i = (n - m) / p; i; i /= p) tim -= i;
    return up1 * down1 % pk * down2 % pk * fpow(p, tim, pk) % pk;
}
IN ll EXLucas(ll n, ll m)
{
    ll ret = 0, M = MOD - 1, Mi;
    for (R int i = 0; i < 4; ++i)
    {
        Mi = M / fact[i];
        ret = (ret + Mi * getinv(Mi, fact[i]) % M * C(n, m, fact[i], fact[i]) % M) % M;
    }
    return ret;
}
int main(void)
{
    in(N), in(G); ll M = MOD - 1;
    if(!(G % MOD)) return putchar('0'), 0;
    ll lim = std::sqrt(N), mul = 0;
    for (R int i = 1; i <= lim; ++i)
    {
        if(!(N % i))
        {
            mul = (mul + EXLucas(N, i)) % M;
            if(N / i != i) mul = (mul + EXLucas(N, N / i)) % M;
        }
    }
    printf("%lld", fpow(G, mul, MOD));
}

然后发现这样搞太慢了…… 这里 p 的次数都为 1 , 直接 l u c a s 定理大力上, 预处理40000以内的阶乘就好了。

代码如下:

#include <cstdio>
#include <cctype>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define W while
#define ll long long
#define MX 100050
#define MOD 999911659
template <class T>
IN void in(T &x)
{
    x = 0; R char c = gc;
    for (; !isdigit(c); c = gc);
    for (;  isdigit(c); c = gc)
    x = (x << 1) + (x << 3) + c - 48;
}
ll pre[MX], fact[4] = {2, 3, 4679, 35617}, fac[MX], N, G;
int pcnt;
IN ll fpow(ll x, ll tim, ll mod)
{
    ll ret = 1;
    W (tim)
    {
        if(tim & 1) ret = ret * x % mod;
        x = x * x % mod; tim >>= 1;
    }
    return ret;
}
void exgcd(ll a, ll b, ll &x, ll &y)
{
    if(!b) return x = 1, y = 0, void();
    exgcd(b, a % b, x, y);
    ll buf = x; x = y, y = buf - a / b * y;
}
IN ll getinv(ll tar, ll mod)
{
    ll x, y;
    exgcd(tar, mod, x, y);
    return (x % mod + mod) % mod;
}
IN ll C(ll n, ll m, ll mod)
{
    if (n < m) return 0;
    return fac[n] * getinv(fac[m] * fac[n - m] % mod, mod) % mod;
}
IN ll lucas(ll n, ll m, ll mod)
{
    if(!m) return 1;
    return lucas(n / mod, m / mod, mod) * C(n % mod, m % mod, mod) % mod;
}
IN ll CRT(ll n, ll m)
{
    ll ret = 0, M = MOD - 1, Mi;
    for (R int i = 0; i < 4; ++i)
    {
        Mi = M / fact[i];
        fac[0] = fac[1] = 1;
        for (R int j = 2; j < fact[i]; ++j) fac[j] = fac[j - 1] * j % fact[i];
        ret = (ret + Mi * getinv(Mi, fact[i]) % M * lucas(n, m, fact[i]) % M) % M;
    }
    return ret;
}
int main(void)
{
    in(N), in(G); ll M = MOD - 1;
    if(!(G % MOD)) return putchar('0'), 0;
    ll lim = std::sqrt(N), mul = 0;
    for (R int i = 1; i <= lim; ++i)
    {
        if(!(N % i))
        {
            mul = (mul + CRT(N, i)) % M;
            if(N / i != i) mul = (mul + CRT(N, N / i)) % M;
        }
    }
    printf("%lld", fpow(G, mul, MOD));
}

猜你喜欢

转载自blog.csdn.net/LPA20020220/article/details/82497526