51nod 1575 Gcd and Lcm

本文针对一道复杂的数论竞赛题目进行了详细的解析,通过数学变换和性质应用,将原问题简化并给出了高效的求解方法。
部署运行你感兴趣的模型镜像

题目:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1575

题意:
T 组数据,每组数据给出一个正整数 n ,求 ni=1ij=1ik=1lcm(gcd(i,j),gcd(i,k))mod232
T10,n109

题解:
观察到 j k 是无关的、可轮换的,不妨尝试将 j k 化简成相同的形式。
注意到 ji[gcd(i,j)=d]=jdid[gcd(id,jd)=1]=φ(id) ,所以我们可以对原式进行初步化简:

i=1nj=1ik=1ilcm(gcd(i,j),gcd(i,k))=i=1nj=1ik=1id1d2[gcd(i,j)=d1][gcd(i,k)=d2]lcm(d1,d2)=i=1nd1|id2|ilcm(d1,d2)j=1i[gcd(i,j)=d1]k=1i[gcd(i,k)=d2]=i=1nd1|id2|ilcm(d1,d2)φ(id1)φ(id2)

为了消除 d1 d2 相互的限制,我们需要将 lcm(d1,d2) 拆开。
对于一般情况,不难得到 lcm(n,m)=d[gcd(nd,md)=1]ndmdd=d|n,d|md|nd,d|mdμ(d)d2nddmddd ,所以我们可以尝试进行代换:

i=1nd1|id2|ilcm(d1,d2)φ(id1)φ(id2)=i=1nd1|id2|id|d1,d|d2d|d1d,d|d2dμ(d)d2d1ddd2dddφ(id1dddd)φ(id2dddd)=i=1nd|id|iddμ(d)d2d1dd|iddd1ddφ(iddd1dd)d2dd|iddd2ddφ(iddd2dd)=i=1nd|id|iddμ(d)d2(d′′dd|iddd′′ddφ(iddd′′dd))2

在上面的最后一步,终于将 j k 对应的 d1 d2 表示成类似的形式,并且上式可以表示成:

i=1nd|id|iddμ(d)d2(d′′dd|iddd′′ddφ(iddd′′dd))2=i=1nxyz=ixμ(y)y2(d|zdφ(zd))2=i=1n(idid2μ(idφ)2)(i)

其中 表示狄利克雷卷积。

因此所求是一个积性函数的前缀和,不妨设 f=idid2μ(idφ)2 ,可以发现 f(pk)=pk1((2k+1)φ(pk+1)+1)(k>0) ,或者是 f(pk)=p2f(pk1)+φ(pk)(2pk1)(k>0) ,可以尝试利用筛法来计算答案。

这里首先给出 f(pk) 的复杂推导,令 g=idφ,h=idid2μ ,不难得到 g(pk)=(k+1)pkkpk1(k>0) h(1)=1,h(pk)=pk(1p)(k>1) ,则有

f(pk)=pi|pkg2(pi)h(pki)=g2(pk)+i=0k1((i+1)piipi1)2)pki(1p)=g2(pk)+pki=0k1(i+1)2pi+1+(i+1)(3i+1)pii(3i+2)pi1+i2pi2=g2(pk)+pk(i=0ki2pi+i=0k1(i+1)(3i+1)pii=0k2((i+1)(3i+5)pi)+i=0k3(i+2)2pi)

由于 i2+(i+3)(3i+1)(i+1)(3i+5)+(i+2)2=0 ,故只需考虑 i=1,k2,k1,k 时的系数。现在考虑上和式外面的 g2(pk) ,则有
f(pk)[pk1]=1f(pk)[p2k2]=k2(k2)2+(k1)(3k5)(k1)(3k1)=0f(pk)[p2k1]=2k(k+1)(k1)2+k(3k2)=2k1f(pk)[p2k]=(k+1)2k2=2k+1

f(pk)=(2k+1)(p2kp2k1)+pk1=pk1((2k+1)φ(pk+1)+1) ,但是这样做非常麻烦,不妨回到原式来做如下推导
f(pk)=i=1pkj=1pklcm(gcd(i,pk),gcd(j,pk))=i=0kj=0kpmax(i,j)φ(pki)φ(pkj)=i=0kpiφ(pki)(2j=0i1φ(pkj)+φ(pki))=pk(2pk1)+i=0k1φ(pk)(2pk(pki+pki1))=(2k+2)p2k2kp2k1pki=0k1(p2kip2ki2)=(2k+1)(p2kp2k1)+pk1

相对简单许多。

由于本题模数固定,且 N=max(n) 不是很大,所以我采取了分段打表的做法。比如要计算 f(x)(x[L,R]) 的值,可以先筛出不超过 R 的质数,再枚举每个质数去计算对 f(x)(x[L,R]) 的贡献,这样做的复杂度是 O(Rx=LΩ(x)+RlnR)=O((RL+1)loglogR+RlnR) ,选取一个恰当的阈值 K ,分别计算 [1,K],[K+1,2K],,[N1KK+1,N] 的值,在本地打表装入代码,在处理询问时只需要处理一个长度不超过 K2 的区间,这样的复杂度是 O(T(KloglogN+NlnN)) 的,本地打表的复杂度和所有数字的质因子个数之和正相关。

显然 K 越小越好,但这样会增加代码的长度,需要具体问题具体分析。对于本题,所存储的值不超过 mod=232 ,用十进制表示需要 10 个字节,用十六进制表示需要 8 个字节,用八十五进制表示需要 5 个字节(ASCII 可见字符集可以支持九十三进制),用 p 进制表示需要 logpmod 个字节。
假设可以用 M 个字节存表,则 logpmodNKM ,取 K=logpmodNM 即可。

代码:(略去打表相关的代码)

#include <cmath>
#include <stdio.h>
#include <algorithm>
const int maxn = 31623, delta = 100000, maxd = 10001, maxl = delta + 1;
int tot, pr[maxn], d[maxn], tf[maxd];
int solve(int L, int R) // [L, R]
{
    if(L > R)
        return 0;
    static int _val[maxl], _rem[maxl];
    int *val = _val - L, *rem = _rem - L, lim = (int)ceil(sqrt(R));
    for(int i = L; i <= R; ++i)
    {
        val[i] = 1;
        rem[i] = i;
    }
    for(int i = 0; i < tot && pr[i] <= lim; ++i)
        for(int j = R / pr[i] * pr[i]; j >= L; j -= pr[i])
        {
            rem[j] /= pr[i];
            int cnt = 1, pk = pr[i];
            for( ; rem[j] >= maxn && rem[j] % pr[i] == 0; rem[j] /= pr[i], ++cnt, pk *= pr[i], val[j] *= pr[i]);
            if(rem[j] < maxn)
                for( ; d[rem[j]] == pr[i]; rem[j] /= pr[i], ++cnt, pk *= pr[i], val[j] *= pr[i]);
            val[j] *= (cnt << 1 | 1) * (pr[i] - 1) * pk + 1;
        }
    int ret = 0;
    for(int i = L; i <= R; ++i)
    {
        if(rem[i] > 1)
            val[i] *= 3 * (rem[i] - 1) * rem[i] + 1;
        ret += val[i];
    }
    return ret;
}
inline int solve(int n) // [1, n]
{
    int idx = n / delta, low = idx * delta, upp = low + delta;
    return n - low <= upp - n ? tf[idx] + solve(low + 1, n) : tf[idx + 1] - solve(n + 1, upp);
}
int main()
{
    for(int i = 2; i < maxn; ++i)
    {
        if(!d[i])
            pr[tot++] = d[i] = i;
        for(int j = 0, k; (k = i * pr[j]) < maxn; ++j)
        {
            d[k] = pr[j];
            if(d[i] == pr[j])
                break;
        }
    }
    // parse table data from string to array, satisfied that tf[i] = solve(1, i * delta).
    int t;
    scanf("%d", &t);
    while(t--)
    {
        int n;
        scanf("%d", &n);
        printf("%u\n", solve(n));
    }
    return 0;
}

您可能感兴趣的与本文相关的镜像

Langchain-Chatchat

Langchain-Chatchat

AI应用
Langchain

Langchain-Chatchat 是一个基于 ChatGLM 等大语言模型和 Langchain 应用框架实现的开源项目,旨在构建一个可以离线部署的本地知识库问答系统。它通过检索增强生成 (RAG) 的方法,让用户能够以自然语言与本地文件、数据库或搜索引擎进行交互,并支持多种大模型和向量数据库的集成,以及提供 WebUI 和 API 服务

题目 51nod 3478 涉及一个矩阵问题,要求通过最少的操作次数,使得矩阵中至少有 `RowCount` 行和 `ColumnCount` 列是回文的。解决这个问题的关键在于如何高效地枚举所有可能的行和列组合,并计算每种组合所需的操作次数。 ### 解法思路 1. **预处理每一行和每一列变为回文所需的最少操作次数**: - 对于每一行,计算将其变为回文所需的最少操作次数。这可以通过比较每对对称位置的值是否相同来完成。 - 对于每一列,计算将其变为回文所需的最少操作次数,方法同上。 2. **枚举所有可能的行和列组合**: - 由于 `N` 和 `M` 的最大值为 8,因此可以枚举所有可能的行组合和列组合。 - 对于每一种组合,计算其所需的最少操作次数,并取最小值。 3. **计算操作次数**: - 对于每一种组合,需要计算哪些行和列需要修改,并且注意行和列的交叉点可能会重复计算,因此需要去重。 ### 代码实现 以下是一个可能的实现方式,使用了枚举和位运算来处理组合问题: ```python def min_operations_to_palindrome(matrix, row_count, col_count): import itertools N = len(matrix) M = len(matrix[0]) # Precompute the cost to make each row a palindrome row_cost = [] for i in range(N): cost = 0 for j in range(M // 2): if matrix[i][j] != matrix[i][M - 1 - j]: cost += 1 row_cost.append(cost) # Precompute the cost to make each column a palindrome col_cost = [] for j in range(M): cost = 0 for i in range(N // 2): if matrix[i][j] != matrix[N - 1 - i][j]: cost += 1 col_cost.append(cost) min_total_cost = float('inf') # Enumerate all combinations of rows and columns rows = list(range(N)) cols = list(range(M)) from itertools import combinations for row_comb in combinations(rows, row_count): for col_comb in combinations(cols, col_count): # Calculate the cost for this combination cost = 0 # Add row costs for r in row_comb: cost += row_cost[r] # Add column costs for c in col_comb: cost += col_cost[c] # Subtract the overlapping cells for r in row_comb: for c in col_comb: # Check if this cell is part of the palindrome calculation if r < N // 2 and c < M // 2: if matrix[r][c] != matrix[r][M - 1 - c] and matrix[N - 1 - r][c] != matrix[N - 1 - r][M - 1 - c]: cost -= 1 min_total_cost = min(min_total_cost, cost) return min_total_cost # Example usage matrix = [ [0, 1, 0], [1, 0, 1], [0, 1, 0] ] row_count = 2 col_count = 2 result = min_operations_to_palindrome(matrix, row_count, col_count) print(result) ``` ### 代码说明 - **预处理成本**:首先计算每一行和每一列变为回文所需的最少操作次数。 - **枚举组合**:使用 `itertools.combinations` 枚举所有可能的行和列组合。 - **计算成本**:对于每一种组合,计算其成本,并考虑行和列交叉点的重复计算问题。 ### 复杂度分析 - **时间复杂度**:由于 `N` 和 `M` 的最大值为 8,因此枚举所有组合的时间复杂度为 $ O(N^{RowCount} \times M^{ColCount}) $,这在实际中是可接受的。 - **空间复杂度**:主要是存储预处理的成本,空间复杂度为 $ O(N + M) $。 ### 相关问题 1. 如何优化矩阵中行和列的枚举组合以减少计算时间? 2. 在计算行和列的交叉点时,如何更高效地处理重复计算的问题? 3. 如果矩阵的大小增加到更大的范围,如何调整算法以保持效率? 4. 如何处理矩阵中行和列的回文条件不同时的情况? 5. 如何扩展算法以支持更多的操作类型,例如翻转某个区域的值?
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值