CF1430 E. String Reversal(div 2)

题目链接:http://codeforces.com/contest/1430/problem/E

题意:有一串长度为n(n<=2*10^5)由小写字母组成的字符串,求通过相邻交换得到其反转串(回文串)得最少交换次数

思路:通过这道题学会了一些奇怪而又实用的小知识(雾,一般相邻交换会想到逆序对数---->一个数列的逆序对数是其通过相邻交换恢复为自然序列的最小交换次数

   因此我们可以将字符串逆序给予编号,这样的话就可以用逆序对数解决问题啦

 

   但是!!!怎么解决相同的字符?我们已知相同的字符间是等价的,因此如果我们要使交换次数最小,不妨使相同字符的编号从小排到大(使其内部间的逆序对数=0)

   实现方式是枚举a-z,分别得到相同字符的子串,然后反转编号,复杂度为O(26*n+n)   (或者在输入时可以用链表优化,复杂度O(2*n))

   操作完后用树状数组求逆序对数即可

 

代码:

#include <cstdio>
#define lowbit(x) x&(-x)
#define LL long long
char s[200100];
int n,a[200100],cnt,head[200100],num;
LL ans;
struct BIT{
    int tr[200100];
    void add(int x)
    {
        for(;x<=n;x+=lowbit(x)) tr[x]++;
        return;
    }
    LL query(int x)
    {
        LL sum=0;
        for(;x;x-=lowbit(x)) sum+=tr[x];
        return sum;
    }
}P;
void exch(char x)
{
    int t;
    num=0;
    for(int i=1;i<=n;i++)
    if (s[i]==x) head[++num]=i;
    for(int i=1;i<=num/2;i++)
    {t=a[head[i]]; a[head[i]]=a[head[num-i+1]]; a[head[num-i+1]]=t;}
    return;
}
int main()
{
    scanf("%d",&n); cnt=1;
    scanf(" %s",s+1);
    a[n]=cnt;
    for(int i=n-1;i>=1;i--)
    {
        if (s[i]!=s[i+1]) cnt++;
        a[i]=cnt;
    }
    for(int i=0;i<26;i++) exch('a'+i);
    for(int i=1;i<=n;i++)
    {
        ans+=P.query(cnt)-P.query(a[i]);
        P.add(a[i]);
    }
    printf("%lld",ans);
    return 0;
}

 

posted @ 2020-10-23 14:39  立志马院的newbee  阅读(96)  评论(1编辑  收藏  举报