NKOJ 4223 彩色方块
问题描述
何老板最近在玩一款叫“彩色方块”的小游戏,游戏虽然简单,但何老板仍旧乐此不疲。
游戏中有n个彩色方块成一排,方块的颜色用字母表示,给出目标排列,只要把它们排成跟目标一样的排列,就算过关。每次操作只能交换相邻两个方块。
给出一关游戏,何老板想知道,最少操作几次就能过关,请你帮他计算最少所需的操作步数。
输入格式
第一行,一个整数n表示方块的数量 第二行,n个大写字母,表示一开始方块的排列情况。每个字母表示对应方块的颜色。
第三行,n个大写字母,表示方块的目标排列情况。
输出格式
一行,一个整数,表示计算结果。 数据保证有解。
样例输入 1
3
RGB
GBR
样例输出 1
2
样例输入 2
4
AACB
BACA
样例输出 2
4
提示
样例1解释: RGB -> GRB -> GBR
对于100%的数据:2<=n<=1,000,000
就是求逆序对个数。
注意到“每次操作只能交换相邻两个方块”,想到了原来排序的题目:
给出一个数列。每次操作可以交换数列中相邻的两个数,求至少操作多少次,才能使数列变为非严格单调递增。
答案显然是原数列的逆序对个数。
本题只是改了一下,其本质还是这个模型。以样例输入2为例,如果将目标数列“BACA”标号为“1 2 3 4”,将原数列“AACB”标号为“2 4 3 1”,那么就完全变成上题的模型了。
简单地说,编号方法为:将目标数列编号为1~N,根据相应字母在目标数列中的编号来给原数列编号。如果同一字母有多个,则从小到大编号。
如:样例输入2中,A字母出现了两次,那么给原数列的两个A字母分别编为2,4。
求逆序对可以用归并排序实现,也可以通过树状数组实现。两个算法时间复杂度都是O(nlogn),权衡空间复杂度和编程难度,当然是优选树状数组。
注意N的范围较大,逆序对数量可能会超过int范围。
贴上辣鸡代码
#include<stdio.h>
#include<vector>
#define ll long long
using namespace std;
vector<int>h[30];
const int MAXN=1000005;
int N,cur[30],A[MAXN];ll Ans;
char ch[MAXN],t[MAXN];
ll C[MAXN];
void Modify(int x,ll d)
{
for(int i=x;i<=N;i+=(i&-i))C[i]+=d;
}
ll GetSum(int x)
{
ll sum=0,i;
for(i=x;i;i-=(i&-i))sum+=C[i];
return sum;
}
int main()
{
int i,tmp,x;
scanf("%d",&N);
scanf("%s%s",ch,t);
for(i=1;i<=N;i++)h[t[i-1]-'A'].push_back(i);//给目标数列编号
for(i=1;i<=N;i++)
{
x=ch[i-1]-'A';//找出原数列中字母对应编号,cur[x]用来标记当前讨论到'A'+x字母的序数
tmp=h[x][cur[x]];
cur[x]++;
Ans+=i-1-GetSum(tmp);
Modify(tmp,1);
}
printf("%lld",Ans);
}