洛谷 P2480 [SDOI2010]古代猪文 题解【欧拉定理】【CRT】【Lucas定理】

博客围绕猪文文字研究代价的数论综合题展开。给定 \(N\)、\(G\),求 \(G^{\sum_{d|n}\mathrm C_n^d}\bmod 999911659\)。当 \(G\) 与 \(999911659\) 互质时用欧拉定理,不互质时特判。通过质因数分解 \(999911658\),利用 Lucas 定理和 CRT 求解,时间复杂度 \(O(\log^2n)\)。

数论综合题。

题目背景

题目背景与题目无关因此省略。题目链接

题目描述

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

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

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

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

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

输入输出格式

输入格式:

输入有且仅有一行:两个数 \(N,G\),用一个空格分开。

输出格式:

输出有且仅有一行:一个数,表示答案除以 \(999911659\) 的余数。

输入输出样例

输入样例#1:

4 2

输出样例#1:

2048

说明

\(10\%\) 的数据中,\(1\le N\le 50\)

\(20\%\) 的数据中,\(1\le N\le 10^3\)

\(40\%\) 的数据中,\(1\le N\le 10^5\)

\(100\%\) 的数据中,\(1\le G\le 10^9,1\le N\le 10^9\)

题解

题意即给定 \(N,G\),求
\[ G^{\sum_{d|n}\mathrm C_n^d}\bmod 999911659 \]
\(G\bot 999911659\) (二者互质)时,指数方面可以用欧拉定理化为 \(\sum_{d|n}\mathrm C_n^d\bmod 999911658\)

\(999911659\) 是质数,所以 \(\varphi(999911659)=999911658\),当且仅当 \(G=999911659\) 时不成立,此时原式恒为 \(0\),注意这里要特判。

而满足 \(d|n\) 的数最多只有 \(O(\log n)\) 个,这样的枚举也最多只进行了 \(O(\sqrt n)\) 次,问题的关键就转到了如何快速求出 \(\mathrm C_n^d\) 上了。

我们知道,当模数为质数时,可以使用 Lucas 定理来做到 \(O(\log p)\) 求出组合数模 \(p\) 意义下的结果,且空间复杂度最小为 \(O(p)\)。此时既不满足模数为质数,也无法满足空间需求。

考虑质因数分解 \(999911658​\) 这个数字,得到 \(2\times 3\times 4679\times 35617​\),它的质因数的次数都是 \(1​\)。利用这个性质我们可以做什么呢?

\(\mathrm C_n^d\equiv x\pmod{999911658}\),由于 \(999911658\) 有上面四个质因数,所以把
\[ \left\{ \begin{aligned} x\equiv a_1&\pmod 2\\\ x\equiv a_2&\pmod 3\\\ x\equiv a_3&\pmod {4679}\\\ x\equiv a_4&\pmod {35617} \end{aligned} \right. \]
这个方程组合并,会得到 \(x\equiv a\pmod{999911658}\)\(a\) 就是我们要求的答案了。

而根据 Lucas 定理,上面那四个模数都是质数,所以可以求出 \(a_1,a_2,a_3,a_4\),最后用 CRT 合并一下就可以了。

时间复杂度 \(O(\log^2n)\)。(分别是求 \(d\) 的复杂度和 Lucas 定理的复杂度)

一定要特判 \(G=999911658\)

Code:

#include<cstdio>
#include<cstring>
#define ll long long
ll fact[40000],inv[40000],p;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(!b)
    {
        x=1,y=0;
        return a;
    }
    ll g=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return g;
}
ll qpow(ll x,ll y)
{
    ll ans=1;
    while(y)
    {
        if(y&1)
            ans=ans*x%p;
        x=x*x%p;
        y>>=1;
    }
    return ans;
}
ll qmul(ll x,ll y)
{
    ll ans=0;
    if(x<0)
        x=p+x;
    if(y<0)
        y=p+y;
    while(y)
    {
        if(y&1)
            ans=(ans+x)%p;
        x=(x+x)%p;
        y>>=1;
    }
    return ans;
}
ll C(ll n,ll m)
{
    if(n<m)
        return 0;
    if(n<p&&m<p)
        return fact[n]*inv[m]%p*inv[n-m]%p;
    return C(n/p,m/p)*C(n%p,m%p)%p;
}
int d[1000],cnt=0;
ll a[5],b[5]={0,2,3,4679,35617};
int main()
{
    ll n,g,x,y;
    scanf("%lld%lld",&n,&g);
    if(g==999911659)
    {
        puts("0");
        return 0;
    }
    for(int i=1;i*i<=n;++i)
        if(n%i==0)
        {
            d[++cnt]=i;
            if(i*i!=n)
                d[++cnt]=n/i;
        }
    //先算 T%2
    for(int l=1;l<=4;++l)
    {
        p=b[l];
        fact[0]=1;
        for(int i=1;i<p;++i)
            fact[i]=fact[i-1]*i%p;
        inv[p-1]=qpow(fact[p-1],p-2);
        for(int i=p-2;i>=0;--i)
            inv[i]=inv[i+1]*(i+1)%p;
        for(int i=1;i<=cnt;++i)
            a[l]=(a[l]+C(n,d[i]))%p;
    }
    for(int i=2;i<=4;++i)
    {
        ll g=exgcd(b[i],b[i-1],x,y);
        p=b[i-1]/g;
        x=qmul(x,(a[i]-a[i-1])/g);
        p*=b[i];
        a[i]-=qmul(x,b[i]);
        a[i]=(a[i]%p+p)%p;
        b[i]=p;
    }
    p=999911659;
    printf("%lld\n",qpow(g,(a[4]%b[4]+b[4])%b[4]));
    return 0;
}

转载于:https://www.cnblogs.com/wjyyy/p/lg2480.html

P2516 题目涉及的是一个字符串处理和动态规划相关的挑战。题目要求对一个由数字组成的字符串进行分割,使得每个分割出的数字子串能构成一个递增序列,并且每部分对应的数值都比前一部分大。以下是解题思路及实现方法。 ### 问题解析 - 输入是一个长度不超过 **40** 的纯数字字符串。 - 目标是将该字符串分割成若干个非空数字子串,这些子串所表示的数值形成一个严格递增序列。 - 每个分割出来的子串必须满足其数值大于前一个子串的数值。 - 最终输出所有可能的合法分割方案的数量。 ### 解法概述 此问题可以通过 **深度优先搜索 (DFS)** 或 **回溯法** 来解决: - 使用递归的方式尝试在每一个位置进行分割。 - 对于每一次分割,提取当前子串并转换为整数,然后判断它是否大于上一次分割得到的值。 - 如果符合递增条件,则继续递归处理剩余的字符串部分。 - 当遍历完整个字符串并且满足所有分割条件时,计数器加一。 ### 实现细节 - 因为输入字符串长度最大为 **40**,所以需要考虑大数问题(超过 `int` 范围),建议使用 `long long` 类型或 Python 中的 `int` 类型自动处理大整数。 - 在分割过程中,确保没有前导零(除非子串长度为 1)。 - 递归终止条件是字符串已经被完全分割。 ### 示例代码 (C++) ```cpp #include <iostream> #include <string> using namespace std; int count = 0; // 将字符串 s 的 [start, end) 子串转换为整数 long long to_number(const string &s, int start, int end) { long long num = 0; for (int i = start; i < end; ++i) { num = num * 10 + (s[i] - '0'); } return num; } // DFS 函数:从 pos 位置开始分割,last_num 表示上一次分割出的数 void dfs(const string &s, int pos, long long last_num) { if (pos == s.size()) { count++; return; } for (int i = pos + 1; i <= s.size(); ++i) { // 剪枝:如果子串长度大于1且以0开头,则跳过 if (i - pos > 1 && s[pos] == '0') break; long long current = to_number(s, pos, i); if (current > last_num) { dfs(s, i, current); } } } int main() { string s; cin >> s; dfs(s, 0, -1); // 初始时 last_num 设置为 -1,保证第一个数可以任意选择 cout << count << endl; return 0; } ``` ### 算法复杂度分析 - 时间复杂度:最坏情况下为指数级 $O(2^n)$,因为每次递归都有多个分支。 - 空间复杂度:主要取决于递归栈深度,最多为 $O(n)$。 这种方法适用于题目给定的数据规模(字符串长度 ≤ 40),通过适当的剪枝优化,可以在合理时间内完成计算。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值