每日两题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;
}
相关推荐
诗9趁年华13 分钟前
深入分析线程池
java·jvm·算法
九年义务漏网鲨鱼1 小时前
【大模型面经】千问系列专题面经
人工智能·深度学习·算法·大模型·强化学习
源码之家2 小时前
机器学习:基于大数据二手房房价预测与分析系统 可视化 线性回归预测算法 Django框架 链家网站 二手房 计算机毕业设计✅
大数据·算法·机器学习·数据分析·spark·线性回归·推荐算法
Lv Jianwei2 小时前
Longest Palindromic Substring最长回文子串-学习动态规划Dynamic Programming(DP)
算法
WWZZ20252 小时前
快速上手大模型:深度学习7(实践:卷积层)
人工智能·深度学习·算法·机器人·大模型·卷积神经网络·具身智能
l1t2 小时前
用SQL求解advent of code 2024年23题
数据库·sql·算法
10岁的博客3 小时前
二维差分算法高效解靶场问题
java·服务器·算法
轻微的风格艾丝凡3 小时前
锂电池 SOC 估计技术综述:成熟算法、新颖突破与车企应用实践
算法·汽车
Codeking__3 小时前
动态规划算法经典问题——01背包问题
算法·动态规划
R-G-B3 小时前
归并排序 (BM20 数组中的逆序对)
数据结构·算法·排序算法