你不妨试着爱上一个复制党,你说什么他就说什么,他从来不会和你唱反调,只要你敢说,他就敢复制。
每日两题
一、基础题
题目:P1015 [NOIP 1999 普及组] 回文数
思路:
- 读入进制 n 和数字字符串 s。数字必须作为字符串输入以保留每一位(包括 A--F 等字母)。
- 将字符串转换成数值数组,注意为了方便大整数按位相加,使用"低位在前(least-significant-digit first)"的存储方式:arr[0] 是最低位。
- 回文判断直接比较数组两端是否相等(对低位优先的存储方式依然成立)。
- 每一步:
- 判断当前数组是否为回文,若是输出 STEP=当前步数并结束。
- 否则构造当前数字的"反转数"(把数组反序),按进制 n 做大整数相加,得到新的数组(仍保持低位在前),处理好进位。
- 最多重复 30 步,超过则输出 "Impossible!"。
- 时间复杂度:O(30 × L),L 为数字长度(每步做一次回文判断和一次按位相加)。
代码(C++,带注释)
时间复杂度 O(30 × L)
c++:
cpp
#include <bits/stdc++.h>
using namespace std;
int n; // 进制
vector<int> arr; // 数字按低位优先存储(arr[0] = 最低位)
map<char, int> mp = {
{'0',0},{'1',1},{'2',2},{'3',3},{'4',4},{'5',5},
{'6',6},{'7',7},{'8',8},{'9',9},{'A',10},{'B',11},
{'C',12},{'D',13},{'E',14},{'F',15}
};
// 将当前数与其反转数相加,结果仍保存在 num 中(低位优先)。
void bigint_add(vector<int>& num) {
vector<int> rev = num; // 复制当前数
reverse(rev.begin(), rev.end()); // rev 现在是"反转数"的低位优先表示
vector<int> res;
int carry = 0;
int len = max((int)num.size(), (int)rev.size());
for (int i = 0; i < len; ++i) {
int a = (i < (int)num.size()) ? num[i] : 0; // 当前数第 i 位(低位优先)
int b = (i < (int)rev.size()) ? rev[i] : 0; // 反转数第 i 位
int sum = a + b + carry;
res.push_back(sum % n); // 当前位结果
carry = sum / n; // 进位
}
if (carry > 0) res.push_back(carry); // 最高位进位
// 去除高位的多余零(通常不会有,但以防)
while (res.size() > 1 && res.back() == 0) res.pop_back();
num.swap(res); // 用计算结果替换原数
}
// 判断当前数组是否为回文(低位优先也可直接比较两端)
bool is_pali(const vector<int>& num) {
int l = 0, r = (int)num.size() - 1;
while (l < r) {
if (num[l] != num[r]) return false;
++l; --r;
}
return true;
}
void solve() {
cin >> n; // 读入进制
string s;
cin >> s; // 数字以字符串形式读入(可能包含字母)
arr.clear();
// 将字符串转换为数字并按低位优先存储
for (int i = (int)s.size() - 1; i >= 0; --i) {
char c = s[i];
if (isalpha(c)) c = toupper(c);
// 假设输入合法且每个字符在 mp 中有定义
arr.push_back(mp[c]);
}
// 最多进行 30 次尝试
for (int step = 0; step <= 30; ++step) {
if (is_pali(arr)) {
cout << "STEP=" << step << '\n';
return;
}
// 否则 arr = arr + reverse(arr)
bigint_add(arr);
}
cout << "Impossible!" << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}
c语言
c
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define MAXD 3000 // 最大位数(低位优先存储)
int n; // 进制
int arr[MAXD]; // 存储大整数,低位在前:arr[0] 是最低位
int len; // 当前有效长度
// 将单字符(0-9, A-F)转换为对应的数值
int char_to_val(char c) {
if (isdigit((unsigned char)c)) return c - '0'; // 数字字符
return toupper((unsigned char)c) - 'A' + 10; // 字母 A-F
}
// 将当前数与其反转数相加,结果仍保存在 arr 中(低位优先)
// arr 表示的数 + reverse(arr) 表示的数,按进制 n 做加法
void bigint_add() {
int rev[MAXD]; // 反转数的位数组(低位优先)
int res[MAXD]; // 保存结果的数组(低位优先)
int i, carry = 0;
int maxlen = len; // 反转数与原数长度相同
// 构造反转数(把高位变为低位)
for (i = 0; i < len; ++i) rev[i] = arr[len - 1 - i];
int reslen = 0;
// 按位相加,注意可能有进位
for (i = 0; i < maxlen; ++i) {
int a = (i < len) ? arr[i] : 0; // 原数第 i 位(低位优先)
int b = (i < maxlen) ? rev[i] : 0; // 反转数第 i 位
int sum = a + b + carry;
res[reslen++] = sum % n; // 当前位结果
carry = sum / n; // 进位
}
// 处理剩余进位
while (carry > 0) {
res[reslen++] = carry % n;
carry /= n;
}
// 去除高位多余的零(保留至少一位)
while (reslen > 1 && res[reslen - 1] == 0) --reslen;
// 将结果写回 arr,并更新长度
for (i = 0; i < reslen; ++i) arr[i] = res[i];
len = reslen;
}
// 判断当前数组是否为回文(低位优先存储下两端比较即可)
int is_pali() {
int i, j;
for (i = 0, j = len - 1; i < j; ++i, --j) {
if (arr[i] != arr[j]) return 0; // 任意一对不等则不是回文
}
return 1;
}
int main() {
char s[MAXD];
if (scanf("%d", &n) != 1) return 0; // 读入进制
if (scanf("%s", s) != 1) return 0; // 读入数字字符串(可能包含字母)
// 初始化 arr:把字符串按低位优先存入 arr(s 最右侧是最低位)
len = strlen(s);
for (int i = 0; i < len; ++i) {
arr[i] = char_to_val(s[len - 1 - i]); /* 低位优先存储 */
}
// 最多尝试 30 步:每步判断是否为回文,若不是则 arr = arr + reverse(arr)
for (int step = 0; step <= 30; ++step) {
if (is_pali()) { // 如果当前为回文
printf("STEP=%d\n", step); // 输出步数并结束
return 0;
}
bigint_add(); // 否则执行一次加法
}
// 超过 30 步仍未成为回文
printf("Impossible!\n");
return 0;
}
二、提高题
题目:小红的不动点权值
思路:
题目中,不动点的定义为将数组a排序后 a i = i ( 1 ≤ i ≤ len ( a ) ) a_i=i\ (1 \leq i \leq \text{len}(a)) ai=i (1≤i≤len(a)) 的数量 ,我们可以发现,要使 a i = i a_i=i ai=i 成立,前提必须是 a k = k ( 1 ≤ k ≤ i − 1 ) a_k=k\ (1 \leq k \leq i-1) ak=k (1≤k≤i−1) 。
- 比如,对于样例
1 4 2 3来说,
包含1的子数组有1、1 4、1 4 2、1 4 2 3, 对于每一个子数组的贡献分别都为1;
包含2的子数组有2、2 3、4 2、1 4 2、1 4 2 3, 对于每一个子数组的贡献分别为0, 0, 0, 1, 1;
包含3的子数组有3、2 3、4 2 3、1 4 2 3, 对于每一个子数组的贡献分别为0, 0, 0, 1;
包含4的子数组有4、1 4、4 2、4 2 3、1 4 2、1 4 2 3, 对于每一个子数组的贡献分别都为0, 0, 0, 0, 0, 1。
总贡献为 4 + 2 + 1 + 1 = 8 4 + 2 + 1 + 1 = 8 4+2+1+1=8
所以,我们可以计算,对于每一个 i ( 1 ≤ i ≤ n ) i(1 \leq i \leq n) i(1≤i≤n) ,对于答案的贡献是多少。
我们可以用一个数组 pos 记录每个数字在原数组中的位置。对于每个 i i i,我们维护当前 1 1 1 到 i i i 的最小和最大下标 l , r l, r l,r,那么只要区间 [ l , r ] [l, r] [l,r] 内恰好包含 1 1 1 到 i i i,这个区间就是一个合法的不动点区间。对于每个 i i i,以 [ l , r ] [l, r] [l,r] 为区间的所有子数组的数量为 l × ( n − r + 1 ) l \times (n - r + 1) l×(n−r+1),累加即可得到答案。
代码(c++):
时间复杂度 O( n n n)
cpp
#include <bits/stdc++.h>
using namespace std;
void go() {
int n;
cin >> n;
vector<int> a(n + 1), pos(n + 1);
for (int i = 1; i <= n; ++i) {
cin >> a[i];
pos[a[i]] = i; // 记录每个值的位置(1-based)
}
long long ans = 0;
int l = INT_MAX, r = INT_MIN;
for (int i = 1; i <= n; ++i) {
l = min(l, pos[i]); // 维护 1..i 的最小下标
r = max(r, pos[i]); // 维护 1..i 的最大下标
// 所有包含区间 [l,r] 的子数组都会在排序后把 1..i 放到前 i 个位置
ans += 1LL * l * (n - r + 1); // 起点可选 1..l,终点可选 r..n
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
// 如果有多组测试,取消下面注释并读入 t
// cin >> t;
while (t--) go();
return 0;
}