[NOIp1998 T2] 拼数(字符串,排序)

该博客介绍了如何解决NOIP1998年的T2问题,即如何通过排序正整数形成一个最大的多位整数。博主提出将数字视为字符串,并以特定方式排序,即如果字符串a+b小于b+a,则b应该在a之前,以此作为排序依据,最终实现高效求解最大整数的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

描述

设有n个正整数(n≤20),将它们联接成一排,组成一个最大的多位整数。
例如:n=3时,3个整数13,312,343联接成的最大整数为:34331213
又如:n=4时,4个整数7,13,4,246联接成的最大整数为:7424613

输入

第一行,一个正整数n。
第二行,n个正整数。

输出

一个正整数,表示最大的整数

输入样例

3
13 312 343

输出样例

34331213

解题思路

(注:以下“a+b”形式表示a字符串后接上b字符串所得字符串)

如果直接暴搜,复杂度高达O(n!)O(n!),即使加了剪枝也无济于事。
那么这道题多半是以一种特定的方式排序后输出。
怎么排序呢?
我们把数字看成字符串,考虑构成一个字符串中的任意两个子字符串a和b,若a+b < b+a,那么显然我们把b放在a的前面更优。举个例子,100+10 < 10+100,所以10100比10010更优(这个例子也可以排除直接比较a和b的大小的做法)。因此,我们就以此为排序依据,排序后一次输出即可。


Code

用C++的string是真的方便!

#include<cstdio>
#include<string>
#include<iostream>
#include<algorithm>

using namespace std;

int n;
string s[25];
bool cmp(string a, string b){
    return a + b > b + a;
}

int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) cin >> s[i];
    sort(s+1, s+n+1, cmp);
    for(int i = 1; i <= n; i++) cout << s[i];
    return 0;
}
<think>我们面对的是一个字符串变换问题,要求使用至多6个规则,在10步内将初始字符串A变换为目标字符串B。由于规则量少且步限制严格,我们可以采用广度优先搜索(BFS)来寻找最短变换路径。BFS适合求解最短路径问题,每一步扩展所有可能的变换。 ### 算法设计 1. **据结构**: - 使用队列存储待处理的字符串状态(以及当前步)。 - 使用集合(`set`)记录已访问的字符串状态,避免重复搜索和循环。 2. **搜索过程**: - 初始状态:将字符串`A`加入队列,步为0。 - 对于队列中的每个字符串,尝试应用所有规则进行变换。对于每条规则,在当前字符串中查找所有可以应用该规则的位置(注意:可能有多个位置)。 - 对于每个可应用的位置,生成新字符串,并检查是否为目标字符串`B`。如果是,则返回当前步+1。 - 如果新字符串未被访问过,则将其加入队列和已访问集合。 3. **步限制**: - 题目要求10步以内,因此当步超过10时,可以终止搜索并输出"NO ANSWER!"。 4. **规则应用**: - 对于每条规则`(a_i, b_i)`,在当前字符串中查找子串`a_i`出现的所有位置。注意,可能在同一字符串中有多个位置可以应用同一条规则,每个位置生成一个新状态。 ### 注意事项 - **去重**:已访问集合非常重要,因为不同的变换路径可能产生相同的中间字符串,避免重复搜索可以显著提高效率。 - **多个匹配位置**:一条规则可能在同一个字符串中有多个匹配位置,每个位置都要尝试。 - **字符串操作**:使用标准库的字符串查找和替换函。注意,查找函要能查找所有位置(例如,使用`find`并循环直到找不到)。 ### 复杂度分析 - 最坏情况下,每个状态最多有6条规则,每条规则可能在字符串中最多出现$O(n)$次($n$为字符串长度,不超过20),因此每个状态最多扩展$6 \times n$个新状态。 - 由于步限制为10,且字符串长度不超过20,状态总不会太大(但最坏情况可能达到$(6 \times 20)^{10}$,但实际上由于字符串长度限制和去重,实际状态会少很多)。 ### 代码实现 我们将使用C++标准库的队列和集合。注意,步超过10则终止并输出"NO ANSWER!"。 ```cpp #include <iostream> #include <queue> #include <set> #include <string> #include <vector> using namespace std; struct Node { string str; int step; // 当前步 }; int main() { string A, B; cin >> A >> B; vector<pair<string, string>> rules; string a, b; while (cin >> a >> b) { rules.push_back(make_pair(a, b)); } // 如果初始字符串就是目标字符串 if (A == B) { cout << 0 << endl; return 0; } queue<Node> q; set<string> visited; q.push({A, 0}); visited.insert(A); while (!q.empty()) { Node cur = q.front(); q.pop(); // 如果步超过10,则不再继续(因为题目要求10步内) if (cur.step > 10) { break; } // 尝试每条规则 for (auto &rule : rules) { string pattern = rule.first; string replacement = rule.second; string s = cur.str; // 查找所有匹配位置 int pos = 0; while ((pos = s.find(pattern, pos)) != string::npos) { // 生成新字符串:将pattern替换为replacement string newStr = s; newStr.replace(pos, pattern.length(), replacement); // 如果新字符串等于目标B if (newStr == B) { cout << cur.step + 1 << endl; return 0; } // 如果新字符串未被访问过,且步+1<=10 if (visited.find(newStr) == visited.end() && cur.step + 1 <= 10) { visited.insert(newStr); q.push({newStr, cur.step + 1}); } // 移动位置以避免同一位置重复匹配(但注意,替换后字符串已变,所以这里是在原字符串上继续查找) pos++; // 移动到下一个位置继续查找 } } } // 如果队列空或超过10步未找到 cout << "NO ANSWER!" << endl; return 0; } ``` ### 关键点说明 1. **查找所有位置**:使用`find`函的循环,每次从上一个匹配位置的下一个字符开始查找,直到找不到为止。 2. **步控制**:在节点入队时,如果步已经超过10,则不再入队。同时,在搜索过程中,如果步超过10,则终止搜索。 3. **去重**:使用集合`visited`记录已经访问过的字符串,避免重复状态。 ### 样例测试 输入样例: ``` abcd xyz abc xu ud y y yz ``` 程序应输出`3`,符合预期。 ### 相关问题 1. 为什么BFS适合求解最短变换步问题? 2. 如何优化字符串替换操作的性能? 3. 如果规则量较大(比如超过6个),如何设计更高效的搜索策略? 4. 双向BFS是否适用于此问题?如何实现?</think>### 子串变换问题求解(NOIP2002 提高组T2) #### 问题分析 给定初始字符串 $A$ 和目标字符串 $B$,以及至多6条变换规则(形如 $A_i \to B_i$),要求找到在10步内将 $A$ 变换为 $B$ 的最短路径。这是一个典型的状态空间搜索问题,适合使用**广度优先搜索(BFS)** 解决,因为BFS天然适合寻找最短路径[^3]。 #### 算法设计 1. **状态表示**:每个状态包含当前字符串和已用步 2. **状态转移**:对当前字符串应用所有可能的规则 - 每条规则可能在字符串中多个位置匹配 - 每个匹配位置生成一个新状态 3. **终止条件**: - 找到目标字符串 $B$ - 步超过10 - 无新状态可生成 #### 优化策略 1. **状态去重**:使用哈希集合记录已访问状态,避免重复搜索 2. **剪枝**:步 > 10 时立即终止搜索 3. **规则预处理**:存储规则列表,避免重复解析 #### BFS实现代码 ```cpp #include <iostream> #include <queue> #include <unordered_set> #include <vector> #include <string> using namespace std; int main() { string A, B; cin >> A >> B; vector<pair<string, string>> rules; string a, b; while (cin >> a >> b) { rules.push_back({a, b}); } queue<pair<string, int>> q; // <当前字符串, 步> unordered_set<string> visited; q.push({A, 0}); visited.insert(A); while (!q.empty()) { auto [cur, steps] = q.front(); q.pop(); if (cur == B) { cout << steps << endl; return 0; } if (steps >= 10) continue; // 步限制 for (auto& [from, to] : rules) { size_t pos = 0; while ((pos = cur.find(from, pos)) != string::npos) { string next = cur; next.replace(pos, from.length(), to); if (visited.find(next) == visited.end()) { visited.insert(next); q.push({next, steps + 1}); } pos++; // 继续查找下一个匹配位置 } } } cout << "NO ANSWER!" << endl; return 0; } ``` #### 算法分析 1. **时间复杂度**: - 最坏情况 $O(6^k \times L)$ - $k$ 为步上限(10),$L$ 为字符串长度(≤202. **空间复杂度**: - $O(6^k \times L)$,由状态决定 #### 样例验证 输入: ``` abcd xyz abc xu ud y y yz ``` 处理流程: 1. 初始状态:`abcd`(步0) 2. 应用规则1:`abcd → xud`(步1) 3. 应用规则2:`xud → xy`(步2) 4. 应用规则3:`xy → xyz`(步3)→ 匹配成功 输出:`3` #### 边界情况处理 1. **初始即目标**:直接返回步0 2. **规则循环**:通过`visited`集合避免 3. **多位置匹配**:`while`循环处理所有匹配位置 4. **空规则**:输入结束判断 #### 优化建议 1. **双向BFS**:同时从$A$和$B$开始搜索,减少状态空间 2. **启发式搜索**:使用编辑距离作为启发函(需在10步限制内) 3. **规则排序**:优先应用长度变化大的规则 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值