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;
}
posted @ 2021-02-19 10:58  Dazzling!  阅读(38)  评论(0编辑  收藏  举报