BZOJ 2789 Letters - 貪心+樹狀數組

首先考慮這樣一個結論:對於第二個串的一個字母X(此字母是第num次出現),要保證交換最小次數,那麼第一個串一定是第num個X移到此位置。然後按此編個號即可完成預處理。
這裏B串的編號爲1…n,問題轉化爲給出一個排列然後交換到升序狀態的最小次數。
計算的話,答案即逆序對的個數,下面給出兩種證明:

證明一

首先假設現在狀態爲前i位已排列完成(A串前i位排列稱爲1…i),需要排的第i+1位(標號爲i+1),於是需要移動的次數就是i+1前比它大的數的個數(因已排列好的部分一定比它小不計算貢獻,而比它大的一定是一段與i+1在左邊相鄰的區間,於是將它們移到右邊,不會打亂這些數的順序)。由此轉移到下一個前i+1位有序的狀態。
可以知道的是排列前i位時比i+1大的且在它左邊的數一定還在它的左邊(即上句不會打亂這些數的順序),於是對於每一個數,初始狀態時前面比它大的數的個數總和即需要交換的次數。
//
舉例說明:
原狀態 3 1 5 2 4
排列1 1 3 5 2 4(貢獻1,即1前比它大的3這一個數)
排列2 1 2 3 5 4(貢獻2,2前有兩個數:3 5)
排列3 1 2 3 5 4(貢獻0)
排列4 1 2 3 4 5(貢獻1)
顯然是逆序對數

證明二(OTZ 27rabbit)

由於沒有數相同,於是交換一次,必定會使逆序對數-1或+1,而可以保證過程中的狀態每次都有使之-1的決策(顯而易見),而沒有-1的決策時即排列完畢。假設一開始有k個逆序對數,最優決策下每次-1,於是k次交換後減至0,排序完成。
//

逆序對拿樹狀數組隨便搞搞就好了。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<vector>
#include<algorithm>

using namespace std;

const int maxn=1000005;
const int maxm=27;

int n;
long long ans;
char a[maxn],b[maxn];
int cnt[maxm],num[maxn],c[maxn];
vector<int>pos[maxm];

int query(int x)
{
    int res=0;
    for(int i=x;i;i-=i&-i)res+=c[i];
    return res;
}
void update(int x)
{
    for(int i=x;i<=n;i+=i&-i)
        c[i]++;
}
int main()
{
    scanf("%d",&n);
    scanf("%s",a+1);
    scanf("%s",b+1);
    for(int i=1;i<=n;i++)
        pos[b[i]-'A'].push_back(i);
    for(int i=1;i<=n;i++)
        num[i]=pos[a[i]-'A'][cnt[a[i]-'A']++];
    for(int i=n;i;i--)
    {
        ans+=query(num[i]);
        update(num[i]);
    }
    printf("%lld",ans);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章