原题链接:http://poj.org/problem?id=3617
问题梗概:给定长度为 的字符串 , 要构造一个长度为 的字符串 。起初, 是一个空串,随后反复进行下列任意操作。
- 从 的头部删除一个字符,加到 的尾部。
- 从 的尾部删除一个字符,加到 的尾部。
目的是要构造字典序尽可能小的字符串 。
限制条件:
- 字符串 只包含大写英文字母
- 输出的字符串每 80 个字符进行一次换行
字典序是指从前到后比较两个字符串大小的方法。首先比较第 1 个字符,如果不同则第 1 个字符较小的字符串更小,如果相同则继续比较第 2 个字符......如此继续,来比较整个字符串的大小。
问题分析:从字典序的性质上看,无论 的末尾有多大,只要前面部分的较小就可以。所以我们可以试一下如下贪心算法:
- 不断取 的开头和末尾中较小的一个字符放到 的末尾。
这个算法已经接近正确了,只是针对 的开头和末尾字符相同的情形还没有定义。在这种情形下,因为我们希望能够尽早使用更小的字符,所以就要比较下一个字符的大小。下一个字符也有可能相同,因此就有如下算法:
- 按照字典序比较 和将 反转后的字符串 ;
- 如果 较小,就从 的开头取出一个文字,追加到 的末尾;
- 如果 较小,就从 的末尾取出一个文字,追加到 的末尾;
- 如果相同则取哪个都可以。
根据前面提到的性质,字典序比较类的问题经常能用得上贪心法。
代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
const int MAX = 2000;
// 输入
int N;
char S[MAX + 1];
void solve() {
// 剩余的字符串为 S[a], S[a+1],...., S[b]
int a = 0, b = N - 1, flag = 0;
while (a <= b) {
// 将从左起和从右起的字符串比较
bool left = false;
for (int i = 0; a + i <= b; i++) {
if (S[a + i] < S[b - i]) {
left = true;
break;
}
else if (S[a + i] > S[b - i]) {
left = false;
break;
}
}
if (left) cout << (S[a++]), flag++;
else cout << (S[b--]), flag++;
if (flag == 80) cout << endl, flag = 0;
}
cout << endl;
}
int main() {
while (cin >> N) {
for (int i = 0; i < N; i++)
cin >> S[i];
solve();
}
return 0;
}