1、新型锁
问题描述
密码学家小蓝受邀参加国际密码学研讨会,为此他设计了一种新型锁,巧妙地融合了数学的严谨性与密码学的安全性。这把锁包含 2025 个连续的数字格,每个格子需填入一个正整数,从而形成一个长度为 2025 的序列 {a1,a2,...,a2025},其中 ai表示第 i 个格子上的数字。
要想解锁,该序列需满足以下条件:任意两个相邻格子中的数字,其最小公倍数(LCM)均为 2025。即对于所有的 i(1≤i≤2024),需满足:LCM(ai,ai+1)=2025现在,请你计算有多少个不同的序列能够解开这把锁。由于答案可能很大,你只需输出其对 10^9+7 取余后的结果即可。
答案提交
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
运行限制
| 语言 | 最大运行时间 | 最大运行内存 |
|---|---|---|
| C++ | 1s | 256M |
| C | 1s | 256M |
| Java | 2s | 256M |
| Python3 | 3s | 256M |
| PyPy3 | 3s | 256M |
| Go | 3s | 256M |
| JavaScript | 3s | 256M |
题解:
题意简述
构造长度为 2025 的正整数序列,满足任意相邻两数的 LCM = 2025,求合法序列总数,答案对 10^9+7 取模。
解题思路
核心结论:能参与构成 LCM(𝑥,𝑦)=2025 的数,一定是 2025 的约数。2025 总共有 15 个约数,状态空间极小,适合 DP。
DP 状态 :dp[i][j] 表示第 i 位填写第 j 个约数的合法方案数。
初始化 :第一位可填任意约数,dp[1][j] = 1。
状态转移 :若 lcm(v[k],v[j]) == 2025,则从上一位状态累加:dp[i][j] += dp[i-1][k],全程取模。
最终答案:累加第 2025 位所有状态的方案数。
复杂度分析
- 时间复杂度:O(2025×15×15),完全满足运行限制
- 空间复杂度:O(2025×15)
AC代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
// dp[i][j]: 第i位选第j个约数的方案数
int dp[2026][15];
// 存储2025的所有约数
vector<int> v;
// 预处理2025的所有约数
void get_factor() {
for (int i = 1; i <= 2025; i++) {
if (2025 % i == 0) v.push_back(i);
}
}
int main() {
get_factor();
int len = v.size();
// 边界初始化:第一位任意约数都合法
for (int i = 0; i < len; i++) dp[1][i] = 1;
// DP递推
for (int i = 2; i <= 2025; i++) {
for (int j = 0; j < len; j++) { // 当前位选v[j]
dp[i][j] = 0;
for (int k = 0; k < len; k++) { // 上一位选v[k]
// 满足相邻LCM=2025则转移
if (lcm(v[j], v[k]) == 2025) {
dp[i][j] = (dp[i][j] + dp[i-1][k]) % mod;
}
}
}
}
// 统计所有合法结尾方案
long long ans = 0;
for (int i = 0; i < len; i++) {
ans = (ans + dp[2025][i]) % mod;
}
cout << ans;
return 0;
}
2、互质藏卡
问题描述
小蓝整理着阁楼上的旧物,偶然发现了一个落满灰尘的卡片箱。打开箱子,里面整齐地摆放着 17600 张卡片,每张卡片上都写有一个数字,恰好包含了从 1 到 17600 的所有正整数。
儿时的他热衷于收集各种卡牌,数量之多令人咋舌。如今,再次翻阅这些尘封的记忆,小蓝不禁感慨万千。他想起收藏家前辈的箴言:"收藏的魅力在于精粹,而非数量"。于是,他决定从这些卡牌中选取 2025 张,组成一套 "互质藏卡"。
"互质藏卡" 的特点在于:任意两张卡片上的数字之间互质,即它们的最大公约数恒为 1。现在,请你帮小蓝计算,共有多少种不同的选取方案,使得选出的 2025 张卡片满足 "互质藏卡" 的条件。由于答案可能很大,你只需给出其对 10^9+7 取余后的结果即可。
注意:两个选取方案被认为是不同的,当且仅当它们所包含的数字集合不完全相同。即,若存在至少一个数字出现在其中一个集合但不出现在另一个集合中,则这两个方案被视为不同。
答案提交
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
运行限制
| 语言 | 最大运行时间 | 最大运行内存 |
|---|---|---|
| C++ | 1s | 256M |
| C | 1s | 256M |
| Java | 2s | 256M |
| Python3 | 3s | 256M |
| PyPy3 | 3s | 256M |
| Go | 3s | 256M |
| JavaScript | 3s | 256M |
题解:
题目大意
从 1∼17600 中选出 2025 个数,满足任意两个数互质,求方案数,答案对 10^9+7 取模。
核心思路
-
互质选数规则 要选出两两互质的数,最优且唯一合法的方式是:
- 从每个质数的幂次集合中选一个数
- 质数的幂次之间两两互质
- 总数量恰好为 2025,与题目要求一致
-
方案数计算 总方案数 = 所有质数的幂次个数相乘 例如:
- 质数 2 的幂:2,4,8...≤17600,共 14 个
- 质数 3 的幂:3,9,27...≤17600,共 8 个
- 最终把所有质数的幂次个数相乘即为答案
-
实现步骤
- 遍历 2∼17600,用试除法判断质数
- 对每个质数,计算它在范围内的幂次个数
- 将所有个数相乘,取模得到答案
代码说明
f1(m):试除法判断质数f2(a,b):快速幂计算 a^b- 主函数遍历所有数,统计每个质数的合法幂次数量,累乘得到答案
AC代码
cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
// 题目范围:数字 1~17600
int n = 17600;
// 答案取模的模数 1e9+7
int mod = 1e9 + 7;
// 函数1:判断一个数是否是质数
// 返回 1 表示是质数,返回 0 表示不是质数
int f1(int m) {
// 1 不是质数
if (m == 1)return 0;
// 2 是质数
if (m == 2)return 1;
// 试除法判断质数:从 2 遍历到 sqrt(m)
for (int i = 2; i <= sqrt(m); i++) {
// 能被整除,说明是合数
if (m % i == 0)return 0;
}
// 循环结束没找到因子,是质数
return 1;
}
// 函数2:快速幂,计算 a^b,用于判断质数的幂是否超过范围
ll f2(ll a, ll b) {
ll ans = 1;
while (b) {
// 如果 b 是奇数,把当前 a 乘到结果里
if (b & 1)ans = ans * a;
// b 右移一位,等价于除以 2
b = b >> 1;
// a 平方
a = a * a;
}
return ans;
}
signed main() {
// 答案初始化为 1(乘法单位元)
ll ans = 1;
// 遍历 2~17600 所有数
for (int i = 2; i <= 17600; i++) {
// 统计当前质数 i 在 17600 范围内的幂次个数
ll cnt = 0;
// 如果 i 是质数
if (f1(i)) {
// 枚举指数 j,计算 i^j
for (int j = 1;; j++) {
// 如果 i^j 不超过 17600,计数 +1
if (f2(i, j) <= 17600)cnt++;
// 超过范围就停止
else break;
}
// 答案 = 答案 × 当前质数的幂次个数,随时取模防止溢出
ans = (ans * cnt) % mod;
}
}
// 输出最终答案
cout << ans;
return 0;
}
3、数字轮盘
问题描述
"数字轮盘" 是一款益智游戏,基于一个带有指针的圆形轮盘展开。轮盘边缘按顺时针刻有数字 1 至 N,初始时指针指向 1。
游戏分为两阶段:旋转轮盘和恢复轮盘。
第一阶段,将轮盘顺时针旋转 K 次。每次旋转,数字依次后移一位,指针指向的数字随之改变。例如,对于 N=4 的轮盘,初始状态为 1, 2, 3, 4(指针指向 1),旋转一次变为 4, 1, 2, 3(指针指向 4),再旋转一次变为 3, 4, 1, 2(指针指向 3),依此类推。
第二阶段,小蓝需通过操作恢复初始状态,每次操作包含以下两步:
• 第一步:翻转以指针为起点、顺时针方向的前 N−1 个数字的顺序。
• 第二步:翻转除指针外的 N−1 个数字的顺序。
例如,对 N=4,状态为 4, 1, 2, 3(指针指向 4)进行一次操作:
• 第一步:翻转 4, 1, 2,变为 2, 1, 4, 3(指针指向 2)。
• 第二步:翻转 1, 4, 3,变为 2, 3, 4, 1(指针指向 2)。
现在,给定轮盘的数字个数 N 和旋转次数 K,请计算小蓝最少需要几次操作才能恢复初始状态。如果无法恢复初始状态,则输出 −1。
输入格式
输入的第一行包含一个整数 T,表示测试用例的数量。
接下来 T 行,每行包含两个整数 N 和 K,分别表示轮盘上的数字个数和旋转次数。
输出格式
输出共 T 行,每行包含一个整数,表示最少需要的操作次数。如果无法恢复初始状态,则输出 −1。
样例输入
2
3 2
4 1
样例输出
评测用例规模与约定
对于 30% 的评测用例,1≤T≤10^2,2≤N≤500,0≤K≤500。
对于 100% 的评测用例,1≤T≤10^5,2≤N≤10^9,0≤K≤10^9。
运行限制
| 语言 | 最大运行时间 | 最大运行内存 |
|---|---|---|
| C++ | 1s | 256M |
| C | 1s | 256M |
| Java | 2s | 256M |
| Python3 | 3s | 256M |
| PyPy3 | 3s | 256M |
| Go | 3s | 256M |
| JavaScript | 3s | 256M |
题解:
题目大意
轮盘顺时针旋转 K 次后,使用规定操作恢复初始状态,求最少操作次数。
关键规律推导(核心)
通过模拟操作可以发现一个决定性结论 : 一次完整的恢复操作,等效于让指针顺时针旋转 2 次。
问题瞬间转化为:
找到最小的操作次数 x,使得 2 * x ≡ k (mod n) 即:x 次操作能把指针转回原位。
公式求解
- k = 0:已经是初始状态,答案 0
- 令
m = n - k。 - n 为奇数 :一定可以恢复
- m 偶数 →
m / 2 - m 奇数 →
(m + n) / 2
- m 偶数 →
- n 为偶数 :
- m 偶数 →
m / 2 - m 奇数 → 无法恢复,输出 -1
- m 偶数 →
代码亮点
- 公式推导,O (1) 求解
- 全程使用 long long 避免溢出
- 严格符合输出格式,每组输出换行
AC代码
cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
signed main() {
ll t;
cin >> t;
while (t--) {
ll n, k;
cin >> n >> k; // n:轮盘数字个数,k:顺时针旋转次数
k %= n; // 旋转 n 次等价于不旋转,取模简化有效旋转次数
if (k == 0) { // 旋转 0 次,已经是初始状态,无需操作
cout << 0 << "\n";
continue;
}
ll m = n - k; // 计算偏移量补数,用于后续奇偶判断
if (n % 2 != 0) { // 情况 1:n 为奇数 → 一定可以恢复
if (m % 2 == 0)
cout << m / 2; // m 偶数,最少操作次数 m/2
else
cout << (m + n) / 2; // m 奇数,最少操作次数 (m+n)/2
} else { // 情况 2:n 为偶数 → 存在无法恢复的情况
if (m % 2 == 0)
cout << m / 2; // m 偶数,可恢复,操作次数 m/2
else
cout << -1; // m 奇数,无法恢复,输出 -1
}
if (t != 0) cout << "\n";
}
}
4、斐波那契字符串
问题描述
斐波那契字符串 S 是由 "0" 和 "1" 所组成的字符串,其生成规则如下:
• S1=0。
• S2=1。
• 对于任意正整数 n(n≥3),Sn=Sn−2+Sn−1("+" 表示字符串拼接)。
例如:S3=01、S4=101、S5=01101。
在斐波那契字符串 S 中,定义逆序对为满足以下条件的整数对 (i,j):
• 1≤i<j≤∣S∣(其中 ∣S∣ 表示 S 的长度)。
• Si=1(第 i 个字符为 "1")并且 Sj=0(第 j 个字符为 "0")。
现在,给定一个正整数 N,请你计算出 SN 中所有逆序对 (i,j) 的总数。由于结果可能很大,请输出其对 10^9+7 取余后的值。
输入格式
输入的第一行包含一个整数 T ,表示测试用例的数量。
接下来的 T 行,每行包含一个整数 N,表示要计算的斐波那契字符串的序号。
输出格式
对于每个测试用例,输出一行,包含一个整数,表示 SN 中所有逆序对的总数对 10^9+7 取余后的结果。
样例输入
2
3
5
样例输出
样例说明
对于 N=3,S3=01,逆序对总数为 0。
对于 N=5,S5=01101,逆序对为 (2,4)、(3,4),总数为 2。
评测用例规模与约定
对于 20% 的评测用例,1≤T≤20,3≤N≤35。
对于 100% 的评测用例,1≤T≤10^5,3≤N≤10^5。
运行限制
| 语言 | 最大运行时间 | 最大运行内存 |
|---|---|---|
| C++ | 1s | 256M |
| C | 1s | 256M |
| Java | 2s | 256M |
| Python3 | 3s | 256M |
| PyPy3 | 3s | 256M |
| Go | 3s | 256M |
| JavaScript | 3s | 256M |
题解:
题目大意
斐波那契字符串定义: S1=0, S2=1, Sn=Sn−2+Sn−1(字符串拼接) 逆序对定义:位置 i<j,满足 Si='1' 且 Sj='0'。 给定 N,求 SN 的逆序对总数,对 10^9+7 取模。
解题思路
1. 定义三个状态
- ai:Si 中
0的数量 - bi:Si 中
1的数量 - ci:Si 中逆序对数量
2. 递推公式(核心)
因为 Sn=Sn−2+Sn−1:
- 字符数量: ai=ai−1+ai−2 bi=bi−1+bi−2
- 逆序对: ci=ci−1+ci−2+bi−2∗ai−1
- ci−2:左串内部逆序对
- ci−1:右串内部逆序对
- bi−2∗ai−1:左串的 1 与右串的 0 形成的跨串逆序对
3. 优化方案
- 预处理到 10^5,每组查询 O(1)
- 全程取模防止溢出
- 时间复杂度 O(10^5+T),可通过 T=10^5 极限数据
代码亮点
- 预处理递推,效率极高
- 状态定义清晰,公式直观
- 适合大数据范围,无超时、无溢出
AC代码
cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
const int MAX = 1e5 + 10;
ll a[MAX]; // a[i]:第i个斐波那契字符串 S_i 中 字符'0'的数量
ll b[MAX]; // b[i]:第i个斐波那契字符串 S_i 中 字符'1'的数量
ll c[MAX]; // c[i]:第i个斐波那契字符串 S_i 中 逆序对的总数量
signed main() {
// S1 = "0"
a[1] = 1; // 0的个数:1
b[1] = 0; // 1的个数:0
c[1] = 0; // 逆序对:0
// S2 = "1"
a[2] = 0; // 0的个数:0
b[2] = 1; // 1的个数:1
c[2] = 0; // 逆序对:0
// 从3开始递推到最大数据范围1e5
for (int i = 3; i <= 1e5; i++) {
// 逆序对总数 = 左边串逆序对 + 右边串逆序对 + 左边1的个数 × 右边0的个数
// 左边 = S_{n-2},右边 = S_{n-1}
c[i] = (c[i-1] + c[i-2] + b[i-2] * a[i-1]) % mod;
// 0的个数:左边0 + 右边0
a[i] = (a[i-1] + a[i-2]) % mod;
// 1的个数:左边1 + 右边1
b[i] = (b[i-1] + b[i-2]) % mod;
}
ll T;
cin >> T;
while (T--) {
ll n;
cin >> n;
cout << c[n] << endl;
}
return 0;
}