原题:[www.luogu.com.cn/problem/P35…]
还有两道思想基本相同的题目 (三倍经验)
[www.luogu.com.cn/problem/P51…]
[www.luogu.com.cn/problem/CF1…]
题面:
P3531 [POI 2012] LIT-Letters
题目描述
给出两个长度相同的的只含大写字母的字符串 ,每次可以交换 中相邻两个字符,求最少的交换次数,使得 交换后的得到的字符串与 相同。
输入格式
输入的第一行是一个整数,代表字符串的长度 。
第二行是一个长度为 的字符串,代表 。
第三行是一个长度为 的字符串,代表 。
输出格式
输出一行一个整数,代表最少的交换次数。
输入输出样例 #1
输入 #1
3
ABC
BCA
输出 #1
2
说明/提示
数据规模与约定
- 对于 的数据,保证 。
- 对于 的数据,, 中只含大写字母,且数据保证 可以变成 。
题目要求最小的交换次数,从而将字符串 变成字符串 。像这类求相邻交换次数的题目,我们通常可以将其转化为求逆序对个数。但是题目给的是字母而非数字,且要求特定的排列,显然不能直接通过字符或者字符串间比较大小来进行逆序对的求解,那么该如何转化呢?
我们来想一想,要求一个序列变成另一个序列,其实和将这个序列排序的原理是一样的,只不过一种是严格和广泛意义上的有序,另一种是由目标序列唯一确定的有序。那我们不妨将目标序列从 到 进行编号,每一个字符对应相应的序号,然后对于传统逆序对,我们是直接通过元素本身的值来比较大小进行排序。在这里我们是通过每个元素对应的唯一确定的下标值来排序。思想大概有点类似于离散化处理?
解决了这个问题,我们还应思考怎么将目标序列对应的下标值映射到原来的序列中,因为我们在这里只有26个字母,可能会出现一个字母对应了多个下标值,比如字母“A”在目标序列的第1、3、7个位置出现了,那么同样是字母“A”,对应的下标值却有三个。那么我们该怎么确定将每个下标值对应到原序列中的哪一个相同的字母呢?
明确题意,我们要求最少的操作次数,即要求最终计算得到的逆序对的数量最少。为尽量使得逆序对数量少,我们应当让原序列和目标序列相同的元素间的距离尽量的小,因为如果距离更大,这个偏移的距离可能会产生更多的逆序对。所以目标就转化成了让相同字符间的偏移距离尽可能小。
首先给出结论:当我们依次按照该字母在目标序列中出现的顺序对原序列中相同字母进行下标赋值时,所得的偏移距离是最小的。例如目标序列:ABBAAC ,对应下标值:123456 , 原序列:BABCAA ,原序列对应下标值:213645
为什么?我们设有两个位置 ,对应两个相同字母在目标序列中出现的位置,同时设 为这两个字母的下标值映射到原序列的下标值,按照我们结论中的策略,应有 对应 , 对应 ,则所得的偏移距离为
我们现在来证明如果交换对应的顺序,即 对应 , 对应 ,所得的新偏移距离 不会更小。
①当 时,交换得 ,维持不变。
②当 时,交换得 ,故 ,距离更大。
③当 时,此时的原距离稍有变化: ,交换得 ,故 ,距离更大。
后面的两种情况与前两种情况等价,不再赘述。
故可以看出当我们按照策略进行映射完成后,不管怎么交换位置,都不可能使其更优。
综上,策略的最优性得证。
cin >> n;
cin >> a >> b;
for (int i = 0; i < n; i++)
{
char c = b[i];
dic[c - 'A' + 1].push(i + 1);
}
vector<ll> ord(n + 1);
for (int i = 0; i < n; i++)
{
char c = a[i];
ord[i + 1] = dic[c - 'A' + 1].front();
dic[c - 'A' + 1].pop();
}
fenwick_tree ft(n);
ll ans = 0;
for (int i = n; i >= 1; i--)
{
ll val = ord[i];
ans += ft.query(val - 1);
ft.update(val, 1);
}
cout << ans;
其中使用了队列来维护出现的第几个位置,每个字母使用一个队列维护。
时间复杂度: ,主要在树状数组的查询和更新上。
对于后两题也是差不多的思路,不过第二题貌似名字不会重复,所以不用那么麻烦,直接用 维护相应值就好,然后第三题使用 函数将原序列反转一下就得到目标序列,后面的过程与第一题相同 (好水的蓝题) 。