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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章