1038 Recover the Smallest Number (30 分)

这个题给(30)分也太拉了。

题意

给出若干可能有前导零的数字串,将它们按某个顺序拼接,使生成的数最小。

思路

很多读者看了样例,会觉得只要把这些数字串按字典序从小到大排序,然后按顺序输出就可以了。这种想法方向似乎是对的。但是来看样例中的例子: {“32”,“321”},排序结果是{“32”,“321”},那么获得的答案就是32321,但是实际上有更小的答案32132。所以这种贪心是错误的。

那么,如何获得正确的答案呢?其实上面的做法已经很接近最小数字了,只是局部上有一些问题。根据上面的反例,可以想到这样的贪心策略:对数字串(S_1)(S_2),如果(S_1+S_2<S_2+S_1)(加号表示拼接),那么把(S_1)放在(S_2)的前面;否则,把(S_2)放在(S_1)的前面。但是这样的策略能否保证结果的正确性呢?

下面尝试进行如下证明:
证明:假设有数字串(S_1+S_2+ cdots +S_{k-1}+S_k+ cdots +S_n),且对任意(i∈[1,n]),有(S_k+S_1<S_1+S_k)成立(也即是说,(S_k)与其他数字串拼接时,(S_k)总是排在前面更优)。那么考虑(S_{k-1}+S_k)部分,显然有(S_k+S_{k-1}<S_{k-1}+S_k)成立,因此(S_1+S_2+ cdots +S_k+S_{k-1}+ cdots +S_n<S_1+S_2+ cdots +S_{k-1}+S_k+ cdots +S_n)成立,这样(S_k)就提前了一个位置。接下来考虑(S_{k-2}+S_k)部分,同理可以得到(S_1+S_2+ cdots +S_k+S_{k-2}+ cdots + S_n<S_1+S_2 + cdots +S_{k-2}+S_k+ cdots +S_n),因此(S_k)又提前了一个位置。
依此类推,最终(S_k)将提前到第一个位置,得到(S_k+S_1+S_2 + cdots +S_{k-1}+S_{k+1}+ cdots +S_n)。同理,对(S_k)之后的部分也可以使用同样的思路将某个数字串提前到(S_k)的后面,使得结果串更小。这样的操作直到所有数字串都处理完毕,就得到了所求的最小数。证毕。

具体实现时,可以将上面的思路写在cmp函数中,然后直接使用sort函数实现。注意:排序后需要去掉整个序列的前导零。

const int N=1e4+10;
string s[N];
int n;

bool cmp(string a,string b)
{
    return a+b < b+a;
}

int main()
{
    cin>>n;

    for(int i=0;i<n;i++) cin>>s[i];

    sort(s,s+n,cmp);

    string res;
    for(int i=0;i<n;i++) res+=s[i];

    int k=0;
    while(res[k] == '0' && k+1 < res.size()) 
        k++;
    cout<<res.substr(k)<<endl;
    //system("pause");
    return 0;
}
原文地址:https://www.cnblogs.com/fxh0707/p/14415071.html