2020第十一届蓝桥杯C/C++ B组省赛-J-字串排序-问题深度分析

题目大意:

给你一个数 V ( V ≤ 10000 ) V(V \leq 10000) V(V10000).让你构造出一个只含小写字母的字符串使得其逆序对恰好 V V V.如果有多个解,输出长度最短的.如果还有解,输出字典序最小的.

题目思路:

1.这个题考虑从前往后一位一位填,找规律后应该先要看出几个结论:

1.1 最终总长度不会很长. 因为逆序对最大可以是 n 2 n^2 n2级别的.(虽然只有26个字母,但是长度大于26后我们还是可以构造一个有 n 2 n^2 n2级别的逆序对)

1.2 最终字符串一定是非递增的.

个人认为这个直觉是很强烈的.因为只有非递增的字符串才能让逆序对尽量大,才可以使得长度能控制的尽量短.

1.3.假设去重后字符串变成 s s s.那么一定满足 s i + 1 = s i + 1 s_{i+1}=s_{i}+1 si+1=si+1.这个也很好证明.

2.现在有了上面三个结论,我们自然能够想出一个算法:

2.1 先枚举长度 1 1 1 V V V.看长度为 i i i的字符串的[最大逆序对]是多少.第一个[最大逆序对]大于等于 V V V的长度一定就是最终长度.

2.2 接着从小到大枚举[只使用前 j j j个字符]是否能够.过程跟上面类似 (因为我们在知道最终长度后,能用的字母越多,逆序对自然也会越大,但字典序也会越大!).

2.3 然后再枚举这个字母 j j j放多少.放的越少越好.

因为根据结论 1.2 1.2 1.2 和 结论 1.3 1.3 1.3.我们最后字符串一定是长这样的:
j , . . , j , j − 1 , . . . , j − 1 , j − 2 , . . , j − 2 , . . . , 1 j,..,j,j-1,...,j-1,j-2,..,j-2,...,1 j,..,j,j1,...,j1,j2,..,j2,...,1.

那么对于当前的 j j j,肯定是放的越少,最终的字符串的字典序才会越小.

2.4 递归一下这个过程,直到 j = 1 j=1 j=1时剩下的全填 a a a即可.

根据上面的过程,我们可以先记忆化搜索预处理一下: d p ( p r e , x , a f t ) dp(pre,x,aft) dp(pre,x,aft)代表前面已经放了 p r e pre pre个字符串,当前放字符 ′ a ′ + x − 1 'a'+x-1 a+x1,且后面还有 a f t aft aft个字符需要放的最大逆序对贡献.自然有一个转移( x x x必须放,因为结论1.3):

d p ( p r e , x , a f t ) = max ⁡ i = 1 a f t { d p ( p r e + i , x − 1 , a f t − i ) + p r e ∗ i } dp(pre,x,aft)=\max_{i=1}^{aft}\{dp(pre+i,x-1,aft-i)+pre*i\} dp(pre,x,aft)=maxi=1aft{ dp(pre+i,x1,afti)+prei}.

根据结论 1.1 1.1 1.1:令 l e n = V len=\sqrt{V} len=V

上述 d p dp dp复杂度为 O ( 26 l e n 3 ) O(26len^3) O(26len3).预处理完这个东西后 d f s dfs dfs的复杂度就从指数级变成了 O ( V ) O(V) O(V)了.

3.所以总时间复杂度为: O ( 26 l e n 3 ) = O ( 26 V V ) O(26len^3)=O(26V\sqrt{V}) O(26len3)=O(26VV ).

PS:实测 l e n ≤ 150 len \leq 150 len150且本地 90 m s 90ms 90ms以内跑完一组.自己的代码和另一种搜索+剪枝的思路跑了大概1000组对拍结果都一样.它那份代码勉强过 V = 10000 V=10000 V=10000的情况, d f s dfs dfs运行次数大概在 1.3 e 8 1.3e8 1.3e8左右.

4.问题进一步拓展:

我这个思路可以解决多次询问.做到 O ( V ) O(V) O(V)的回答.

5.代码:

const int maxn = 1e6 + 5;
const int mod = 1e9 + 7;
int dp[152][26][152];
int dfs (int pre , int x , int aft)
{
    
    
    int &d = dp[pre][x][aft] ;
    if ( ~d ) return d;
    if (x == 1) return d = pre * aft;
    if (aft == 0) {
    
    
        if (x != 0) return -1e9;
        return 0;
    }
    int ans = 0;
    for (int i = 1 ; i <= aft ; i++){
    
    
        int res = dfs(pre + i, x - 1 , aft - i) + i * pre;
        if (ans < res){
    
    
            ans = res;
        }
    }
    return d = ans;
}
void dfs2 (int pre , int x , int aft , int rest , string &res)
{
    
    
    if (x == 1){
    
    
        res += string(aft , 'a');
        return ;
    }
    int len = 1e9;
    for (int i = 1 ; i <= aft ; i++){
    
    
        int d = dfs(pre + i , x - 1 , aft - i) + i * pre;
        if (d >= rest){
    
    
            len = i;
            break;
        }
    }
    res += string(len , 'a' + x - 1);
    rest -= len * pre;
    dfs2(pre + len , x - 1 , aft - len , rest , res);
    return ;
}
int calc (string a)
{
    
    
    int n = a.size();
    int ans = 0;
    for (int i = 0 ; i < n ; i++){
    
    
        for (int j = i + 1 ; j < n ; j++){
    
    
            if (a[i] > a[j]) ans++;
        }
    }
    return ans;
}
void solve (int v)
{
    
    
    memset(dp , -1 , sizeof dp);
    int len = 1e9 , x = 1e9;
    for (int i = 1 ; i <= 150 ; i++){
    
    
        for (int j = 1 ; j <= 26 ; j++){
    
    
            if (dfs(0ll , j , i) >= v){
    
    
                if (len > i){
    
    
                    len = i , x = j;
                }else if (len == i){
    
    
                    x = min(x , j);
                }
            }
        }
    }
    string ans;
    dfs2(0 , x , len , v , ans);
    cout << ans << endl;
    return ;
}
int main()
{
    
    
    ios::sync_with_stdio(false);
    int n; cin >> n;
    solve(n);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_35577488/article/details/114966402
今日推荐