Codeforces 1430E

Educational Codeforces Round 96 (Rated for Div. 2) E. String Reversal


题意

给定一个长度为(n)的字符串,每次操作可以交换两个相邻字符,问将原串倒置所需要的最小操作数。


限制

(2leq nleq 200000)




思路

先将原串(S)倒置存于新字符串(R)内,以下标(i)为循环变量,遍历字符串(R),每次将第(i)个位置的字符从剩余的字符串中移动出来并固定,剩余的字符串称作剩余串

那么就可以模拟字符移动,每次将字符(R[i])移动到剩余串的第一个位置,表示第(i)个位置的字符完成了移动

贪心可得,每次从剩余串中将下标最小(R[i])移动到串首,操作次数最少

那么问题就转化成了记录每个字符剩余串中出现的第一个位置,使用(26)个变量(pos)记录

先遍历原串(S),将每种字符第一次出现的位置记录在(pos)数组内,(cnt)数组记录每种字符出现的次数

遍历(R),对于每个字符(t),取出其在剩余串中第一次出现的位置(pos[t])

由于目的是将其移动到剩余串中的首位置,所以答案增加(pos[t]-1)

然后将原串中(pos[t])位置的一个字符删除,代表剩余串中删去一个字符

其后循环(26)个变量,由于删去字符后(pos[t])其后的字符下标全部(-1),故对于每个(pos[i]>pos[t]),需要让(pos[i]-1)

最后将(cnt[t]-1),如果(cnt[t])非零,则可以直接将指针(pos[t])向右移动寻找下一个字符(t)

最终复杂度为(O(n imes d),d=26)




程序

(405ms/2000ms)

#include<bits/stdc++.h>
using namespace std;

string S,R;
int n,pos[26],cnt[26];

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    
    cin>>n>>S;
    S=" "+S;
    R=S;
    for(int i=1;i<=n;i++)
    {
        int t=S[i]-'a';
        if(pos[t]==0)
            pos[t]=i;
        cnt[t]++;
    }
    
    long long ans=0; //注意答案可能超出int范围
    for(int i=n;i;i--)
    {
        int t=R[i]-'a';
        ans+=pos[t]-1;
        S.erase(pos[t],1); //删去一字符
        cnt[t]--;
        
        for(int j=0;j<26;j++)
            if(pos[j]>pos[t]) //对于每个位置大于pos[t]的
                pos[j]--;
        
        if(cnt[t]==0)
            pos[t]=0;
        else
        {
            while(S[pos[t]]-'a'!=t) //向右继续查找
                pos[t]++;
        }
    }
    cout<<ans<<'
';
    
    return 0;
}

原文地址:https://www.cnblogs.com/stelayuri/p/13799898.html