每日两题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;
}
相关推荐
2401_857918292 分钟前
实时数据处理中的C++应用
开发语言·c++·算法
2401_884563242 分钟前
C++中的装饰器模式实战
开发语言·c++·算法
MicroTech20257 分钟前
微算法科技(NASDAQ :MLGO)抗量子区块链技术:筑牢量子时代的数字安全防线
科技·算法·区块链
Ivanqhz9 分钟前
图着色寄存器分配算法(Graph Coloring)
开发语言·javascript·python·算法·蓝桥杯·rust
Elsa️74611 分钟前
洛谷p5718 复习下快速排序和堆排序
数据结构·算法·排序算法
Frostnova丶13 分钟前
LeetCode 3567.子矩阵的最小绝对差
算法·leetcode·矩阵
夏日听雨眠15 分钟前
文件学习9
数据结构·学习·算法
华农DrLai15 分钟前
什么是自动Prompt优化?为什么需要算法来寻找最佳提示词?
人工智能·算法·llm·nlp·prompt·llama
黎阳之光16 分钟前
十五五智赋新程 黎阳之光以AI硬核技术筑造产业数智底座
大数据·人工智能·算法·安全·数字孪生
2401_8914821717 分钟前
C++中的原型模式
开发语言·c++·算法