题解 CF1430E 【String Reversal】

绝顶我为峰

2020-10-12 21:02:56

Solution

~~难得切一个 E。~~ ~~不过这个确实比较水。/youl~~ 题目大意:将一个字符串翻转,每次可以交换相邻两个字符,求最少交换次数。 看到**交换相邻字符**这种操作,自然想到逆序对,显然逆序对的数量就是交换次数。 那么有一个很 naive 的想法就是给每个字符一个目标位置,生成一个目标序列,然后统计一遍这个序列的逆序对就好了。 难点在于这个字符串里是有相同字符的,这样我们直接将 $i$ 的目标位设置成 $n-i+1$ 显然不是最优解(参见样例) 显然,我们的目标就是要生成一个 $1\sim n$ 的排列 $a$,使得 $\forall1\leq i\leq n,stringvalue_i=stringvalue_{a_i}$,且 $a$ 的逆序对数量最少。(**这里认为原串下标是 $1\sim n$**) 那么自然我们想到对于相同字符,把他们的 $a_i$ 倒序赋值就是最优解。 这个结论比较显然,因为序列 $a$ 越接近升序,他的逆序对就越少。 所以我们统计一下每种字母出现位置,同种字母倒序构造目标序列就好了。 置于实现方法,数组 哈希 $vector$ $queue$ $set$ 什么的全都可以。 ```cpp #include<iostream> #include<cstdio> #include<vector> #include<string> #include<algorithm> using namespace std; #define int long long string s; int n,ans,a[200001],b[200001]; vector<int> v[31]; void qqsort(int l,int r) { if(l==r) return; int mid=(l+r)>>1; qqsort(l,mid); qqsort(mid+1,r); int i=l,j=mid+1,cnt=l-1; while(i<=mid&&j<=r) if(a[i]<=a[j]) b[++cnt]=a[i++]; else { ans+=mid-i+1; b[++cnt]=a[j++]; } while(i<=mid) b[++cnt]=a[i++]; while(j<=r) b[++cnt]=a[j++]; for(register int i=l;i<=r;++i) a[i]=b[i]; } signed main() { scanf("%lld",&n); cin>>s; s=" "+s; for(register int i=1;i<=n;++i) v[s[i]-'a'].push_back(i); for(register int i=0;i<26;++i) for(register int j=0;j<(int)v[i].size();++j) a[v[i][j]]=v[i][v[i].size()-j-1];//倒序赋值 for(register int i=1;i<=n;++i) a[i]=n-a[i]+1;//因为前面赋值的是没有经过倒序的位置,所以这里统一倒序 //for(register int i=1;i<=n;++i) //cout<<a[i]<<endl; qqsort(1,n); printf("%lld\n",ans); return 0; } ```