分析
打表找了一下规律之后发现
如果n=3,一共A(3,3)全排列,有6种情况
0+0+0
0+0+1
0+0+2
0+1+0
0+1+1
0+1+2
这里能看出来第一位的取值范围{0},一种取值
第二位取值范围{0,1},两种取值
第三位取值范围{0,1,2},三种取值
如果n=4呢,简单打表后发现也是符合这个规律的
第一位的取值范围{0},一种取值
第二位取值范围{0,1},两种取值
第三位取值范围{0,1,2},三种取值
第四位取值范围{0,1,2,3},四种取值
n=4,也就是说有A(4,4)全排列,24种情况
在这24种情况中,第一位只有一种取值,这一种取值要相加24次,0*24=0。第二位有两种取值,每一位相加24/2次,也就是12次,0*12+1*12=12。第三位有三种取值,每一位相加24/3次,也就是8次,0*8+1*8+2*8=24。第四位有四种取值,每一位相加24/4,也就是6次,0*6+1*6+2*6+3*6=36。
代码
这就找到基本规律了,开始写代码
我最开始想到如果每一位都要相加 A ( n , n ) 次的话,取这一位上所有取值的平均数然后再乘 A ( n , n ) 不就好了?第 i 位上的取值范围为 0 ~ i ,那直接 i / 2 就可以了
然后我就写出了
错误代码,没有考虑模逆元
mod=998244353
n=int(input())
cont=1
ans=0
for i in range(1,n+1): #算全排列
cont*=i
cont=cont%mod
for i in range(1,n): #算每一位
temp=i/2*cont
ans+=temp
print(int(ans)%mod)
运行之后发现为什么只能过一半测试呢,看了一下别人的题解才知道
在加法,乘法,减法中,一次取模与步步取模的结果是一样的
但是在除法里不一样,一旦取模要用到除法,就得用模逆元了
详情可以看
https://zhuanlan.zhihu.com/p/100587745
我上述代码中用到了i/2,所以如果要继续这个思路的话得换模逆元
正确代码
mod = 998244353
inv2 = pow(2, mod - 2, mod)
# 计算2的模逆元,使用费马小定理,在我上面放的那个链接里有讲
n = int(input())
if n < 2:
print(0)
exit()
cont = 1
ans = 0
# 计算全排列
for i in range(1, n + 1):
cont = (cont * i) % mod
# 计算每一位
for i in range(1, n):
if i % 2 == 0:
temp = (i // 2 * cont) % mod
else:
# 使用模逆元处理除法
temp = (i * cont) % mod
temp = (temp * inv2) % mod
ans = (ans + temp) % mod
print(ans)
有人可能会问,为什么i % 2 == 0的时候不需要模逆元? (没人问你)
因为模逆元的核心作用是处理 无法整除的除法。例如,若需要计算 5 / 2 % mod ,因为 5 不是 2 的倍数,直接整除会得到错误结果(5 // 2 = 2,但实际期望的是 5 * inv(2))。此时必须通过逆元将除法转换为乘法。
但是当 i 是偶数时,i 必定能被 2 整除,因此 i // 2 是精确的整数。进行模运算不会有精度问题