2 条题解
-
0
你好!作为金牌教练,这就为你提供一套专业的 OJ数据生成器 (Data Generator)。
这套代码集成了两部分:
- 标准解法 (Solver):使用前文提到的 中心扩展法,保证生成准确的
.out答案。 - 数据构造逻辑 (Generator):精心设计了 10 个测试点,覆盖了从极小值、边界值、特殊构造(如全字符相同、无回文、长回文)到最大数据范围的随机数据。
你可以直接编译运行此 C++ 代码,它会在当前目录下自动生成
1.in/1.out到10.in/10.out共20个文件。数据生成器 C++ 代码
/* * Title: OJ Data Generator for Longest Palindromic Substring * Description: Generates 10 sets of input (.in) and output (.out) files. * Format: NOI Standard (C++14) * Author: OI Coach */ #include <iostream> #include <fstream> #include <string> #include <vector> #include <algorithm> #include <random> #include <chrono> using namespace std; // ========================================== // Part 1: 标准解法 (Solver) // 用于生成 .out 文件 // ========================================== int expand(const string& s, int left, int right) { while (left >= 0 && right < s.size() && s[left] == s[right]) { left--; right++; } return right - left - 1; } string solve(const string& s) { if (s.empty()) return ""; int start = 0, maxLen = 0; int n = s.size(); for (int i = 0; i < n; ++i) { int len1 = expand(s, i, i); int len2 = expand(s, i, i + 1); int len = max(len1, len2); if (len > maxLen) { maxLen = len; start = i - (len - 1) / 2; } } return s.substr(start, maxLen); } // ========================================== // Part 2: 数据构造工具 (Generator Tools) // ========================================== // 字符集:数字 + 大小写字母 const string CHARSET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 随机数生成器初始化 mt19937 rng(chrono::steady_clock::now().time_since_epoch().count()); // 生成指定长度的随机字符串 string genRandomString(int len) { string s = ""; uniform_int_distribution<int> dist(0, CHARSET.size() - 1); for (int i = 0; i < len; ++i) { s += CHARSET[dist(rng)]; } return s; } // 生成全相同的字符串 string genAllSame(int len, char c) { return string(len, c); } // 生成从不重复的字符串 (abcde...),最大回文长度为1 string genNonPalindrome(int len) { string s = ""; for (int i = 0; i < len; ++i) { // 循环使用 a-z,避免相邻或间隔重复 s += (char)('a' + (i % 26)); } return s; } // 生成一个巨大的回文串 (abc...cba) string genGiantPalindrome(int len) { int halfLen = len / 2; string half = genRandomString(halfLen); string s = half; if (len % 2 == 1) s += CHARSET[rng() % CHARSET.size()]; // 中间加一个随机字符 string revHalf = half; reverse(revHalf.begin(), revHalf.end()); s += revHalf; return s; } // ========================================== // Part 3: 主程序 (Main) // ========================================== int main() { // 定义10个测试点的生成策略 for (int i = 1; i <= 10; ++i) { string input_str; string desc; // 描述(用于调试) // --- 数据构造逻辑 --- switch(i) { case 1: // 样例数据 input_str = "babad"; desc = "Sample Case"; break; case 2: // 极小边界:长度1 input_str = "Z"; desc = "Boundary: Len 1"; break; case 3: // 极小边界:长度2,相同 (偶数回文) input_str = "OO"; desc = "Boundary: Len 2 (Same)"; break; case 4: // 极小边界:长度2,不同 input_str = "Op"; desc = "Boundary: Len 2 (Diff)"; break; case 5: // 小规模随机数据 input_str = genRandomString(50); desc = "Random Small (Len 50)"; break; case 6: // 特殊情况:全相同字符 (最大回文=全长) input_str = genAllSame(1000, 'A'); desc = "All Same Chars (Len 1000)"; break; case 7: // 特殊情况:无长度>1的回文串 input_str = genNonPalindrome(1000); desc = "No Palindrome > 1 (Len 1000)"; break; case 8: // 特殊情况:整个字符串就是回文 input_str = genGiantPalindrome(1000); desc = "Whole Palindrome (Len 1000)"; break; case 9: // 特殊情况:多峰值 (多个等长最长回文) // 构造 "x...xAAAAAx...xBBBBBx...x" // 用 'x' 填充,中间插入固定的回文 { string filler1 = genAllSame(300, 'x'); string filler2 = genAllSame(300, 'y'); // 避免和 filler1 合并 string filler3 = genAllSame(300, 'z'); // "12345" 和 "54321" 长度均为5 input_str = filler1 + "123454321" + filler2 + "abcdeedcba" + filler3; // 注意:这里的长度可能略微超过1000,稍微截断一下符合题意 if(input_str.size() > 1000) input_str.resize(1000); } desc = "Multiple Max Peaks"; break; case 10: // 大规模随机强测 input_str = genRandomString(1000); desc = "Random Large (Len 1000)"; break; } // --- 写入输入文件 (.in) --- string inFileName = to_string(i) + ".in"; ofstream inFile(inFileName); if (!inFile) { cerr << "Error creating " << inFileName << endl; return 1; } inFile << input_str; inFile.close(); // --- 计算并写入输出文件 (.out) --- string outFileName = to_string(i) + ".out"; ofstream outFile(outFileName); if (!outFile) { cerr << "Error creating " << outFileName << endl; return 1; } // 运行标准解法 string ans = solve(input_str); outFile << ans; // NOI格式通常文末可以有换行,也可以没有,string自带无换行,若需换行可加 << endl outFile.close(); cout << "Generated Test Case " << i << ": " << desc << " [Len: " << input_str.length() << "]" << endl; } cout << "\nAll 10 test cases generated successfully." << endl; return 0; }数据点设计说明 (Coverage Analysis)
- Test 1 (Sample):
babad。直接使用题目样例,确保基础理解正确。 - Test 2 (Boundary Min):
Z。长度为1的边界情况。 - Test 3 (Boundary Even):
OO。长度为2且字符相同,测试偶数长度回文的最简情况。 - Test 4 (Boundary Diff):
Op。长度为2且字符不同,测试无回文扩展的情况。 - Test 5 (Random Small): 长度50的随机串。测试常规逻辑。
- Test 6 (Max Pattern): 1000个
A。测试算法在最坏情况下的性能(每次都要扩展到头),以及输出是否为全长。 - Test 7 (Min Pattern):
abcde...。无任何长度大于1的回文串,测试算法是否能正确返回单个字符作为答案。 - Test 8 (Max Palindrome): 1000长度的整体回文(如
ab...ba)。测试大跨度回文的识别。 - Test 9 (Multi Peak): 人工构造包含两个独立的最长回文(例如中间有
123454321和abcdeedcba)。测试题目中“输出任意一个”的要求(标准答案代码会输出先找到的那个,或者根据逻辑输出其中之一,只要符合长度即可)。 - Test 10 (Random Max): 长度1000的完全随机串。模拟真实比赛中的强测数据。
使用方法
- 将上述代码保存为
generator.cpp。 - 使用 g++ 编译:
g++ generator.cpp -o generator -std=c++14。 - 运行:
./generator(Windows下是generator.exe)。 - 程序结束后,你的文件夹里会出现
1.in~10.in和1.out~10.out。
- 标准解法 (Solver):使用前文提到的 中心扩展法,保证生成准确的
-
0
你好!作为你的教练,我很高兴看到你准备进入代码实现的阶段。在考场上,代码的规范性、边界处理的严谨性往往决定了你能不能拿满分。
对于数据范围 的题目,中心扩展法 (Center Expansion) 是最优选的“标准解法”。相比于动态规划 (DP),它不需要额外的 空间,且常数更小,实际运行速度极快。
以下是符合 NOIP/NOI 竞赛风格的 C++14 标准代码。
一、 题解标准代码 (C++14)
/* * 题目: 最长回文子串 (Longest Palindromic Substring) * 算法: 中心扩展法 (Expand Around Center) * 时间复杂度: O(N^2) * 空间复杂度: O(1) * * 考点: 字符串处理、枚举、边界判断 */ #include <iostream> #include <string> #include <algorithm> #include <vector> using namespace std; // 辅助函数:以 left 和 right 为中心向两边扩展 // 如果 left == right,说明以字符为中心(寻找奇数长度回文) // 如果 left + 1 == right,说明以空隙为中心(寻找偶数长度回文) int expand(const string& s, int left, int right) { // 关键点1:边界检查必须写在访问 s[left] 和 s[right] 之前,利用 && 的短路特性防止越界 while (left >= 0 && right < s.size() && s[left] == s[right]) { left--; right++; } // 易错点1:当循环结束时,s[left] != s[right] 或者越界。 // 此时回文串的范围是 [left + 1, right - 1]。 // 长度计算:(right - 1) - (left + 1) + 1 = right - left - 1 return right - left - 1; } int main() { // 竞赛必备:关闭流同步,加速 I/O ios::sync_with_stdio(false); cin.tie(nullptr); string s; if (!(cin >> s)) return 0; // 防止读取失败 int n = s.size(); if (n < 2) { cout << s << endl; return 0; } int start = 0; // 记录最长回文串的起始下标 int maxLen = 0; // 记录最长回文串的长度 for (int i = 0; i < n; ++i) { // 情况A:以当前字符 s[i] 为中心(如 "aba") -> 奇数长度 int len1 = expand(s, i, i); // 情况B:以 s[i] 和 s[i+1] 之间的空隙为中心(如 "abba") -> 偶数长度 int len2 = expand(s, i, i + 1); int len = max(len1, len2); // 关键点2:只有发现更长的回文串时才更新 if (len > maxLen) { maxLen = len; // 易错点2:根据中心位置 i 和长度 len 推导起始位置 start // 举例推导: // 1. 奇数 "aba", i=1, len=3 -> start=0. 公式: 1 - (3-1)/2 = 0 // 2. 偶数 "abba", i=1, len=4 -> start=0. 公式: 1 - (4-1)/2 = 1 - 1 = 0 (注意整除) // 结论:统一公式为 i - (len - 1) / 2 start = i - (len - 1) / 2; } } // 关键点3:不要在循环里做 substr,那样会导致 O(N^3) // 只在最后输出时截取一次 cout << s.substr(start, maxLen) << endl; return 0; }
二、 复杂度分析思考过程
1. 时间复杂度分析
- 思考:代码有一层
for循环遍历字符串的每个字符,这是 。在循环内部,我们调用了expand函数。 - 最坏情况:当字符串全是同一个字符时(如
aaaaa...),对于每一个中心,expand都会向两边扩展直到边界。这意味着每次扩展需要 的时间。 - 计算:总时间复杂度 = 外部循环次数 内部扩展次数 = 。
- 数据范围验证:题目给出 。,计算机一秒大概能跑 次运算,所以 是非常安全的,完全可以通过。
2. 空间复杂度分析
- 思考:我们需要多少额外的内存?
- 分析:我们只用了几个整型变量 (
i,start,maxLen,left,right,len),没有开辟额外的数组或者递归栈。 - 结论:空间复杂度为 (不计算存储输入字符串本身的 空间)。这比动态规划解法的 空间要优秀得多。
三、 进阶优化建议 (针对 N 大很多的情况)
教练提示:虽然上面的代码能过这道题,但如果题目数据范围加强到 或 ,上面的 就会超时 (TLE)。
这时候你需要掌握Manacher 算法 (马拉车算法)。
- 核心思想:利用回文串的对称性,避免重复计算。如果我们知道大回文串里包含了一个小回文串,那么在这个大回文串对称的另一边,通常也有一个相同的小回文串,不需要重新扩展。
- 复杂度:时间 ,空间 。
- NOI 考场策略:
- 首选: 中心扩展法。因为代码短、好写、不易出错,且 时足够快。
- 备选:只有在明确数据范围 时,才考虑写 Manacher,因为那个算法细节繁琐,考场上写错的风险较大。
希望这份标准代码和分析能帮你彻底吃透这道题!把它写在你的代码库里,经常复习其中的边界计算公式。加油!
- 思考:代码有一层
- 1
信息
- ID
- 19339
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 10
- 标签
- (无)
- 递交数
- 1
- 已通过
- 1
- 上传者