String Reversal
Educational Codeforces Round 96 (Rated for Div. 2) - E. String Reversal
题目描述
定义一个操作为交换字符串中相邻的两个字母
给定一个只含有小写字母的字符串 求出从原字符串到翻转之后的字符串需要多少次操作
输入
5
aaaza
输出
2
node
在第一个示例中,您必须交换第三个和第四个元素,因此字符串变为"aazaa"。
然后你必须交换第二个和第三个元素,所以字符串变成了"azaaa"。 因此,可以在两次交换中反转字符串。
分析
我们可以发现每次交换相邻的两个字符 这很像冒泡排序 (冒泡排序中最少的交换次数为 逆序对数
因此这道题就是让我们求逆序对数了,然而这道题有相同的字符出现的可能,就造成了有一些细节问题需要
处理
- 对于样例aaaza 我们可以把这个字符串等效为12345 然而逆序的如果是54321 这样显然是不对的, 因为我们相同
的字符在移动的时候肯定是最靠近的去移动,而不是最远的去移动 因此这个逆序应该是14235 这样显然就会有最小的
操作次数了. - 简单来说就是对于逆序的字符串来说, 某个字符从左到右的数字应该是从小到大排序的 比如样例的逆序为azaaa 显然我们
最左边的那个a 应该是我们所有a所代表的数字中的最小的 因此就是1 后面同理 (这样可以在满足题意的情况下最优
因此呢这道题就变成了一道求逆序对的题 我们可以用树状数组求解.
这里简单讲一下如何用树状数组求逆序对
- 我们把一个数组翻转之后, 我们每次查询这个数字之前的数字出现了多少次 就是原数组中以这个数字开头的逆序对数
- 我们倒序循环原来的数组, 每次求
a[i]
的前n项和, 然后在树状数组中的这个下标+1
即可
分享两道树状数组的例题 - Acwing 楼兰图腾
- 洛谷 逆序对
本题的代码
AC_CODE
#include <iostream>
#include <cstdio>
#include <vector>
#define pb push_back
#define lowbit(i) i & -i
using namespace std;
const int N = 2e5 + 10;
int n;
int tr[N];
long long ans;
vector<int> a[26], id(26);
void add(int x, int c) {
for(int i = x; i <= n; i += lowbit(i))
tr[i] += c;
}
int sum(int x) {
int res = 0;
for(int i = x; i; i -= lowbit(i)) res += tr[i];
return res;
}
int main() {
cin >> n;
string s; cin >> s;
for(int i = 0; i < n; i ++ ) a[s[i] - 'a'].pb(i + 1);
for(int i = 0; i < n; i ++ ) {
int p = s[i] - 'a';
ans += sum(a[p].back());
add(a[p].back(), 1);
a[p].pop_back();
}
cout << ans << endl;
}