ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

题解:洛谷 P3612 Secret Cow Code

2026/8/17 9:53:52 拓冰建站 浏览量
题解:洛谷 P3612 Secret Cow Code

【题目来源】

洛谷:P3612 [USACO17JAN] Secret Cow Code S - 洛谷(luogu.com.cn)

【题目描述】

奶牛们正在实验秘密代码,并设计了一种方法用于生成无限长度的字符串,作为他们代码的一部分。

给定一个字符串 \(s\),令 \(F(s)\)\(s\) 后接 \(s\) 向右“旋转”一个字符的结果(在右旋转中,\(s\) 的最后一个字符旋转并成为新的第一个字符)。给定初始字符串 \(s\),奶牛们通过重复应用 \(F\) 来构建他们的无限长度代码字符串;因此每一步都会使当前字符串的长度翻倍。

给定初始字符串和一个索引 \(N\),请帮助奶牛计算无限代码字符串中第 \(N\) 个位置的字符。

【输入】

输入由一行组成,包含一个字符串和 \(N\)。字符串最多由 30 个大写字母组成,且 \(N \leq 10^{18}\)

请注意,\(N\) 可能太大,无法放入标准的 32 位整数中,因此你可能需要使用 64 位整数类型(例如,C/C++ 中的 "long long")。

【输出】

请输出从初始字符串构建的无限代码字符串的第 \(N\) 个字符。第一个字符的位置为 \(N=1\)

【输入样例】

COW 8

【输出样例】

C

【核心思想】

  1. 问题分析:给定初始字符串 \(s\),通过不断应用 \(F(s) = s + rotate\_right(s)\) 构造无限长字符串(每次长度翻倍)。给定位置 \(N\)\(N \leq 10^{18}\)),求该位置的字符。本质上是递归定位 + 逆向推导问题:利用字符串生成的对称性,从最终长度逆向缩回到初始字符串的对应位置。

  2. 算法选择

    • 逆向推导:不构造无限字符串,而是从目标位置 \(N\) 出发,逆向追踪其在初始字符串中的对应位置
    • 长度倍增分析:每次操作后长度为 \(len \times 2\),后半部分由前半部分右旋一位得到
  3. 关键步骤

    • 读取数据:读入字符串 \(s\) 和位置 \(N\)
    • 计算覆盖长度\(len1 = |s|\)\(len2 = len1\),不断 \(len2 \times 2\) 直到 \(len2 \geq N\)
    • 逆向定位(当 \(N > len1\)):
      • \(k = len2 / 2 + 1\)(后半部分的起始位置)
      • \(N \geq k\)
        • \(N = k\)(恰好是分割点),\(N \leftarrow N - 1\)(对应前半部分的最后一个字符)
        • 否则 \(N \leftarrow N - k\)(映射到前半部分的对应位置)
      • \(len2 \leftarrow len2 / 2\)(回退到上一阶段)
    • 输出 \(s[N-1]\)(字符串 \(0\)-based 索引)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(\log N)\),每次循环长度减半
    • 空间复杂度:\(O(1)\),仅使用常数额外空间
  5. 逆向推导的核心思想

    • 生成规则的对称性\(F(s) = s + s'\),其中 \(s'\)\(s\) 右旋一位的结果,即 \(s'[i] = s[(i-1) \bmod |s|]\)
    • 后半部分的映射:若 \(N\) 在后半部分(\(N > len2/2\)),则 \(N\) 对应前半部分的 \(N - len2/2\) 位置,但需考虑右旋的偏移
    • 分割点的特殊处理\(k = len2/2 + 1\) 是后半部分的第一个位置,对应前半部分的最后一个字符(因右旋),故 \(N = k\) 时映射到 \(k-1\)
    • 对数级复杂度:每次迭代长度减半,\(10^{18}\) 最多约 \(60\) 次迭代,完全可接受
    • 适用于无限序列定位、递归映射、大数处理类问题

【解题思路】

【算法标签】

普及- #字符串入门

【代码详解】

#include <bits/stdc++.h>
using namespace std;int main()
{string s;       // 初始字符串long long len1; // 初始字符串长度long long len2; // 扩展后的字符串长度long long n;    // 要查询的位置long long k;    // 中间变量,用于计算分割点cin >> s >> n;  // 输入初始字符串和查询位置len1 = s.length();  // 获取初始长度len2 = len1;        // 初始化扩展长度// 计算字符串扩展后的最小长度,使其包含位置nwhile (len2 < n)len2 *= 2;  // 每次长度翻倍// 逆向计算原始字符串中对应的字符位置while (n > len1){k = len2 / 2 + 1;  // 计算中间分割点if (n >= k)       // 如果在后半部分{if (n == k)   // 正好是分割点n--;       // 调整到前一个位置elsen = n - k; // 调整到前半部分的对应位置}len2 /= 2;  // 长度减半,回到上一阶段}// 输出结果(注意字符串从0开始索引)cout << s[n - 1];return 0;
}

【运行结果】

COW 8
C