1. 程式人生 > 實用技巧 >CF1430 E. String Reversal(div 2)

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; }