练习-纪律问题(幂运算)
问题描述
一年级一班有 n 个小朋友坐成一排,统计了这些小朋友的爱好后,老师得知了他们一共有 m 种爱好,每个小朋友会拥有这些爱好中的一种。
如果相邻的小朋友爱好相同,那么他们上课时就会忍不住悄悄说话,违反课堂纪律。
老师想知道,一种有多少种状态可能发生违法记录的现象。
答案对 100003取模。
输入格式
输入包括两个整数 m,n含义见上文。
输出格式
输出一个整数,代表模 100003 的意义下共有多少种可能的答案。
样例输入
2 3
样例输出
6
分析问题:
总排列数:
-
每个小朋友有
m
种选择,n
个小朋友的总排列数为:
不发生违法记录的排列数:
-
如果没有任何相邻小朋友的爱好相同,那么:
-
第一个小朋友有
m
种选择。 -
第二个小朋友不能和第一个相同,有
m-1
种选择。 -
第三个小朋友不能和第二个相同,有
m-1
种选择。 -
以此类推,后面的每个小朋友都有
m-1
种选择。
-
-
因此,不发生违法记录的排列数为:
发生违法记录的排列数:
-
发生违法记录的状态数 = 总排列数 - 不发生违法记录的排列数。
解题代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int p = 100003;
// 定义快速幂函数qmi(a, n),用于计算a的n次方模p
ll qmi(ll a, ll n) {
ll res = 1; // 初始化结果为1
while (n > 0) { // 当n大于0时循环
if (n & 1) { // 如果n的最低位是1
res = (res * a) % p; // 将res乘以a并取模p
}
a = (a * a) % p; // 将a自乘并取模p
n >>= 1; // 将n右移一位,相当于n除以2
}
return res; // 返回计算结果
}
int main() {
ll m, n;
cin >> m >> n;
// 计算所有可能的排列数zong,即m的n次方模p
ll zong = qmi(m, n) % p;
// 计算不发生违法记录的排列数buweifa,即m乘以(m-1)的(n-1)次方模p
// 第一个小朋友有m种选择,后面的每个小朋友有(m-1)种选择(不能和前一个小朋友的爱好相同)
ll buweifa = m * qmi(m - 1, n - 1) % p;
// 计算发生违法记录的排列数ans,即总排列数减去不发生违法记录的排列数
ll ans = zong - buweifa;
// 如果ans为负数,则加上p以保证结果为正数
if (ans < 0) {
ans += p;
}
// 输出最终结果,即ans模p
cout << ans % p;
return 0; // 程序正常结束
}
代码说明:
1. 为什么 ll zong = qmi(m, n) % p
和 ll buweifa = m * qmi(m - 1, n - 1) % p
要模上 p
?
原因:
-
qmi
函数内部已经取模:确实,qmi
函数在计算过程中会对每一步的结果取模p
,因此qmi(m, n)
的结果已经是m^n % p
。 -
额外的取模是为了防止溢出:
-
在计算
buweifa = m * qmi(m - 1, n - 1)
时,qmi(m - 1, n - 1)
的结果可能已经接近p
(最大为p-1
)。 -
如果
m
也很大,那么m * qmi(m - 1, n - 1)
的结果可能会超过long long
的范围,导致溢出。 -
因此,在乘法操作后再次取模
p
,可以确保结果不会溢出,同时保持结果的正确性。
-
总结:
-
qmi
函数内部的取模是为了保证幂运算的结果不溢出。 -
外部的取模是为了防止乘法操作后的结果溢出。
2. 为什么最后判断大小后不能直接输出 ans
,而是输出 ans % p
?
原因:
-
ans
的计算可能为负数:-
ans = zong - buweifa
,如果zong < buweifa
,那么ans
会是负数。 -
在模运算中,负数的结果需要调整为正数,因此代码中通过
if (ans < 0) ans += p;
来保证ans
是非负数。
-
-
输出
ans % p
是为了确保结果在模数范围内:-
即使
ans
已经是非负数,它仍然可能大于p
(例如,如果zong
和buweifa
都接近p
,ans
可能接近2p
)。 -
输出
ans % p
可以确保结果始终在[0, p-1]
的范围内,符合题目的要求。
-
总结:
-
判断大小后调整
ans
是为了处理负数情况。 -
输出
ans % p
是为了确保结果在模数范围内,避免结果超出[0, p-1]
。