每日两题day23

你不妨试着爱上一个复制党,你说什么他就说什么,他从来不会和你唱反调,只要你敢说,他就敢复制。

每日两题


一、基础题

题目:P1015 [NOIP 1999 普及组] 回文数

思路:

  • 读入进制 n 和数字字符串 s。数字必须作为字符串输入以保留每一位(包括 A--F 等字母)。
  • 将字符串转换成数值数组,注意为了方便大整数按位相加,使用"低位在前(least-significant-digit first)"的存储方式:arr[0] 是最低位。
  • 回文判断直接比较数组两端是否相等(对低位优先的存储方式依然成立)。
  • 每一步:
    1. 判断当前数组是否为回文,若是输出 STEP=当前步数并结束。
    2. 否则构造当前数字的"反转数"(把数组反序),按进制 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 的子数组有 11 41 4 21 4 2 3, 对于每一个子数组的贡献分别都为1
    包含 2 的子数组有 22 34 21 4 21 4 2 3, 对于每一个子数组的贡献分别为0, 0, 0, 1, 1
    包含 3 的子数组有 32 34 2 31 4 2 3, 对于每一个子数组的贡献分别为0, 0, 0, 1
    包含 4 的子数组有 41 44 24 2 31 4 21 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;
}
相关推荐
妮妮喔妮5 小时前
10.25复习LRU缓存[特殊字符]
算法
linff9115 小时前
hot 100 技巧题
数据结构·算法·leetcode
暴风鱼划水5 小时前
卡码网语言基础课(Python) | 19.洗盘子
python·算法
会编程是什么感觉...5 小时前
算法 - PEC校验
单片机·算法
再卷也是菜6 小时前
算法基础篇(8)贪心算法
算法·贪心算法·1024程序员节
AI科技星6 小时前
接近光速运动下的光速不变性:基于张祥前统一场论的推导与验证
数据结构·人工智能·经验分享·算法·计算机视觉
陈苏同学7 小时前
笔记1.4:机器人学的语言——三维空间位姿描述 (旋转矩阵 - 齐次变换矩阵 - 欧拉角 - 四元数高效表示旋转)
笔记·线性代数·算法·机器人
scx201310047 小时前
20251025 分治总结
数据结构·c++·算法
碧海银沙音频科技研究院7 小时前
DiVE长尾识别的虚拟实例蒸馏方法
arm开发·人工智能·深度学习·算法·音视频