题目传送门: http://soj.me/1198

题意很简单,给出n个字符串,如何拼接这n个字符串,使得得到的字符串字典序最小。

Solutions

这题一个很容易想到但是错误的做法是直接字符串排序,然后拼接起来,这个做法看起来是正确的,但实际是错误,看下面的一个反例:

1
b, ba

按照直接排序得到结果是bba, 但正确答案是bab.

由于题目只有8个字符串,可以直接使用暴力枚举,8!

有一个更好的方法,适用于n更大的情况, 也是对字符串进行排序,但是排序的cmp函数改为如下:

1
2
3
bool cmp(const string& a, const string& b) {
return ab <= ba;
}

然后按照排序结果连接起来即可,

但这个算法的正确性需要依赖传递性(ab<=ba, bc<=cb => ac<=ca),具体证明见下一节。

传递性证明

有下面定理1:

有字符串a,b, 且a < b(字符串比较)
ab <= ba <=> a+…+a <= b+…+b(字符串是无限长)

证明:

充分性

1
2
3
a <= b
=> aa <= ab => aa...aa <= ab...ab (1)
=> ba <= bb => baba..ba <= bb....bb (2)
1
2
ab <= ba
=> abab....ab <= baba....ba (3)

综合(1),(2),(3), 得到:

aa....aa <= ab....ab <= baba..ba <= bb....bb, 得证。

必要性

证明:

a <= b, a+a+…+a <= b+…+b, => ab <= ba

分三种情况:

1) len(a) = len(b)

由 a<=b,结论ab <= ba明显成立。

2) len(a) > len(b)

a<=b => a.substr(0, len(b)) < b, 易证ab <= ba

3) len(a) < len(b)
这种情况证明比较麻烦, 可以分为两种子情况:

3.1) a不是b的前缀,即a.substr(0, len(b)) != b
由a <= b, 可以得出a.substr(0, len(b)) < b, => ab < ba.

3.2) a是b的前缀,即a.substr(0, len(b)) == b
这种情况是整个证明中最难理解和困难的地方。

由a+…+a <= b+…+b, 必然下面两个字符串满足:

aa…aa <= b…b, (左右两边字符串长度相等,总的总长度为len(a)*len(b))

结合上面的不等式,查看下面这幅图:
Untitled.png

因为a是b的前缀,所以

b可以由b1和bn-1两部分组成, b=b1+bn-1, 其中b1=a。

接着a和bn-1对齐接着比较,那么bn-1 = b2+bn-2, 这时可以判断b2 ?< a, 若b2=a, 可以接着划分下去n-3,n-4,直到结尾或得到大小比较。

用形式化语言可以这样说:

假设b=b1b2b3…bi…bn, len(bi) = len(a), len(bn) <= len(a), bn是a的前缀
由aa…aa <= bb…bb, 推出
存在i > 0,使得a <= bi, bj = a(j < i).

查看下面ab和ba的字符串比较,查看下面这幅图:
Untitled2.png

a对应b1,
b1对应b2,
b2对应b3,

bi-1对应bi,

bn-1+bn对应bn+a

由上述推论得到:

存在一个i,使得a = bi-1 <= bi, a+b1+b2+...+bi-1+...+bn <= b1+b2+b3+...+bi+bn+a
ab <= ba

由上述1), 2), 3),结论成立。

由定理1可以推出下面结论,传递性:

字符串a,b,c, 若 ab <= ba, bc <= cb
则有ac <= cb

证明:

1
2
ab <= ba => a...a <= b...b
bc <= cb => b...b <= c...c

取total_len = len(a)*len(b)*len(c), 则有:

1
2
a...a <= ... <= c...c
=> a...a <= c...c => ac <= cb