Papple Sort_arc088_c

53 阅读4分钟

先来考虑一下什么情况有解,如果出现次数为奇数的字符种类数大于等于2,就不行了,可以有一个字符出现奇数次,也可以所有字符都出现偶数次,这两种情况能变成回文串。

要是能确认最终回文串是什么样子的,就可以确定交换次数,因为确定了每个字符从哪个位置到哪个位置。具体地,对于一个相同的字符,他们之间的交换是没有意义的,所以原串中的第i个a,会去到回文串中第i个a的位置。记p[i]:原串中第i个字符(1-indexed,索引从1开始,为了方便)在回文串中的位置。则对p数组求逆序数,得到的就是最小相邻交换次数,这是一个关于相邻交换排序所需最少次数的结论。

对于某个字符a,其在回文串中对称的字符a在原串中唯一确定,例如a在原串中出现了6次,分别在位置i1,i2,i3,i4,i5,i6,则i1位置上的a在回文串中一定对应i6位置上的,同样,i2和i5配对,i3和i4配对。

所以可以将字符看成是成对出现的,则对于两个字符A,B,(B可能和A相等,比如他们都是'a')有以下相对位置的情况:

....A1...A2....B1....B2....(A1和A2最终配成一对,B1和B2最终配成一对,那么A1一定在A2前,B1一定在B2前)

此时最终要么交换成...A1...B1...B2....A2...,要么是.....B1....A1....A2.....B2.....,由于最终交换次数由逆序数决定,这两种情况的逆序数增量都是2,所以对结果的贡献一样。

....A1...B1....A2....B2.... 不论最终是...A1...B1...B2....A2..还是...B1...A1...A2....B2...,交换次数一样。

最后一种情况是: .....A1....B1....B2.....A2.... 此时应该让最终结果串中这些字符的相对顺序是:...A1....B1....B2...A2...这样不需要交换次数。

综合以上所有情况,有一个简单的结论,对于两个字符对,pair1和pair2,第一个字符对的字符出现位置是l1,r1,第二个是l2,r2,那么如果让l小的排在最终串的靠外部分,让l大的排在最终串的靠里部分。

对于A和B是相同的字符的情况呢,怎么排都可以,所以以上策略生效。

以下程序使用树状数组统计逆序对数量。

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

#define lowbit(x) (x&(-x))

const ll maxn=2e5+5;
map<char,vector<ll>> appear;
struct ch_pair{
    ll l,r;
    char ch;
    ch_pair(ll l=0,ll r=0,char ch='#'):l(l),r(r),ch(ch) {}
    bool operator < (const ch_pair &rhs) const {
        return l<rhs.l;
    }
};
ll tree[maxn];
ll n;

void update(ll x,ll d){
    while(x<=n){
        tree[x]+=d;
        x+=lowbit(x);
    }
}

ll query(ll x){
    ll sum=0;
    while(x>0){
        sum+=tree[x];
        x-=lowbit(x);
    }
    return sum;
}

int main()
{
    ios::sync_with_stdio(0);cin.tie(0);

    string s;
    cin>>s;
    //记录每个字符的出现位置
    for(ll i=0;i<s.size();i++){
        appear[s[i]].push_back(i);
    }

    //统计有多少个字符出现了奇数次
    ll odd_num=0;
    char odd_ch='#';
    for(ll i='a';i<='z';i++){
        if(appear[i].size()&1) {
            odd_num++;
            odd_ch=i;
        }
    }
    if(odd_num>=2) cout<<-1<<"\n";
    else {

        vector<ch_pair> p;
        //把出现奇数次的那个字符出现在中间的放到s1中间,其余当偶数次字符处理
        vector<ll> tmp; //tmp记录出现奇数次的那个字符除了最中间的位置外出现的位置
        for(ll i=0;i<appear[odd_ch].size();i++) {
            if(i==appear[odd_ch].size()/2) continue;
            tmp.push_back(appear[odd_ch][i]);
        }
        //将出现奇数次的字符改成出现偶数次的字符
        appear[odd_ch].clear();
        for(ll i=0;i<tmp.size();i++) appear[odd_ch].push_back(tmp[i]);


        for(ll i='a';i<='z';i++){
            for(ll j=0;j<=(ll)appear[i].size()/2-1;j++){
                ll opp=(ll)appear[i].size()-j-1;
                //把每个字符对放入p中
                p.push_back(ch_pair(appear[i][j],appear[i][opp],i));
            }
        }
        stable_sort(p.begin(),p.end());


        /*for(ll i=0;i<p.size();i++){
            printf("l=%lld r=%lld ch=%c\n",p[i].l,p[i].r,p[i].ch);
        }*/

        string s1;
        n=s.size();
        s1.resize(n+10);    //s1:结果回文串
        for(ll i=0;i<p.size();i++){
            s1[i]=s1[n-i-1]=p[i].ch;
        }

        //别忘了出现次数为奇数的那个特殊字符
        if(odd_ch!='#'){
            ll center=n/2;
            s1[center]=odd_ch;
        }

        /*cout<<s1<<"\n";
        printf("Runs to HERE\n");*/

        //appear:每个字符的原出现位置
        //appear1:结果串中的新位置
        ll pos[maxn];
        appear.clear();
        for(ll i=0;i<n;i++) appear[s[i]].push_back(i);
        map<char,vector<ll>> appear1;
        for(ll i=0;i<n;i++) appear1[s1[i]].push_back(i);
        for(ll i='a';i<='z';i++){
            for(ll j=0;j<(ll)appear[i].size();j++){
                pos[appear[i][j]+1]=appear1[i][j]+1;
            }
        }

        /*for(ll i=1;i<=n;i++) cout<<pos[i]<<" ";
        cout<<"\n";*/

        //统计逆序数
        ll ans=0;
        for(ll i=n;i>=1;i--){
            ans+=query(pos[i]);
            update(pos[i],1);
        }
        cout<<ans<<"\n";
    }
    return 0;
}