P1039 · OFFICIAL SOLUTION

P1039 翻转字符 官方题解

Gioush OJ · P1039 翻转字符

翻转字符

【题意简述】

给出字符串 AA,记其翻转串为 BB。每次可以交换 BB 中相邻的两个字符,求把 BB 变为 AA 的最少交换次数。

【Hint】

【提示】

相同字符按出现顺序进行对应不会更劣。建立 BB 中每个字符对应到 AA 中位置的排列后,答案就是这个排列的逆序对数量。

【数据点 1∼21\sim 21∼2】

用二进制集合 SS 表示已经从 BB 中取出的位置。令 k=∣S∣k=\lvert S\rvert,下一步枚举 p∉Sp\notin S 且 Bp=Ak+1B_p=A_{k+1},把 BpB_p 移到未取出字符的最前方。代价是它左侧尚未取出的字符数量。

状态数为 2n2^n。时间复杂度为 O(n22n)\mathcal{O}(n^2 2^n),空间复杂度为 O(2n)\mathcal{O}(2^n)。

【数据点 3∼53\sim 53∼5】

直接维护当前字符串。从左到右处理目标位置 ii,在当前位置之后找到第一个等于 AiA_i 的字符,再通过相邻交换把它移动到位置 ii。时间复杂度为 O(n2)\mathcal{O}(n^2)。

【正解】

先确定重复字符的对应方式。固定字符 xx,设它在 AA 中依次出现于

p1<p2<⋯<pk,p_1<p_2<\cdots<p_k,

在 BB 中依次出现于

q1<q2<⋯<qk.q_1<q_2<\cdots<q_k.

可以令 qiq_i 对应 pip_i。若 qi<qjq_i<q_j 却分别对应 ps>ptp_s>p_t,交换两个相同字符的目标位置不会改变最终字符串,并且会消去这一次交叉。对于任意第三个目标位置,交换也不会增加逆序关系。因此反复消去交叉后,可以得到按出现顺序对应的最优方案。

按照 BB 中字符的顺序,依次取出该字符在 AA 中尚未使用的最早位置,得到一个排列 P1,P2,…,PnP_1,P_2,\ldots,P_n。

若 i<ji<j 且 Pi>PjP_i>P_j,这两个字符在初始串与目标串中的相对顺序不同,二者至少交换一次。一次相邻交换只改变一对字符的相对顺序,所以操作次数至少为排列的逆序对数量。

另一方面,按照冒泡排序逐一消去逆序对,每次交换都使逆序对数量减少 11,最终恰好得到目标顺序。因此最少交换次数就是排列的逆序对数量。

不需要显式构造 BB。先把 AA 中每种字符的出现位置从左到右放入队列,再倒序扫描 AA,每次弹出对应队列的队首位置,便可得到排列。随后使用线段树维护已经出现的位置,查询此前加入且大于当前位置的元素数量。

【复杂度分析】

每个位置执行一次查询与一次修改。时间复杂度为 O(nlog⁡n)\mathcal{O}(n\log n),空间复杂度为 O(n)\mathcal{O}(n)。答案最大为 n(n−1)2\dfrac{n(n-1)}{2},需要使用 long long。

【参考代码】

#include <queue>#include <cstdio>#define MAXN 200010using namespace std; int c,n,Line[MAXN],Tree[MAXN<<2];char Str[MAXN];queue <int> Pos[26]; void Modify(int Now,int l,int r,int x) {    if (l==r) {        Tree[Now]=1;        return;    }    int Mid=(l+r)>>1;    if (x<=Mid) Modify(Now<<1,l,Mid,x);    else Modify(Now<<1|1,Mid+1,r,x);    Tree[Now]=Tree[Now<<1]+Tree[Now<<1|1];} int Query(int Now,int l,int r,int x,int y) {    if (x>y||l>y||r<x) return 0;    if (l>=x&&r<=y) return Tree[Now];    int Mid=(l+r)>>1;    return Query(Now<<1,l,Mid,x,y)+Query(Now<<1|1,Mid+1,r,x,y);} int main() {#ifndef ONLINE_JUDGE    freopen("flip.in","r",stdin);    freopen("flip.out","w",stdout);#endif    scanf("%d",&c);    scanf("%d%s",&n,Str+1);    for (int i=1;i<=n;i++) Pos[Str[i]-'a'].push(i);    for (int i=n;i>=1;i--) {        int x=Str[i]-'a';        Line[n-i+1]=Pos[x].front();        Pos[x].pop();    }    long long Ans=0;    for (int i=1;i<=n;i++) {        Ans+=Query(1,1,n,Line[i]+1,n);        Modify(1,1,n,Line[i]);    }    printf("%lld\n",Ans);    return 0;}