题解 CF1430E 【String Reversal】

题目链接

Solution CF1430E String Reversal

题目大意:给定一个字符串,每次操作可以交换两个相邻字符,询问最少多少次操作可以让字符串翻转

树状数组


分析:

首先考虑一个 (n^2) 算法,我们找出字符串与翻转串不同的位置,往后找到第一个需要交换的位置,修改字符串并且统计答案

rev = str;
reverse(rev.begin(),rev.end());
for(int i = 0;i < str.size();i++){
	if(str[i] != rev[i]){
		for(int j = i + 1;j < str.size();j++)
			if(str[j] == rev[i]){
				for(int k = j;k >= i + 1;k--)
					str[k] = str[k - 1];
				str[i] = rev[i];
				ans += j - i;
				break;
			}
	}
}

这样无法通过,需要优化,对于每种字符单独考虑,维护每个字符出现的位置,我们需要支持的操作:

  • 1.删除第一个数
  • 2.将小于某个阈值的所有数全部加 (1)
  • 3.询问第一个数

可以通过差分,然后树状数组维护。位置单调,因而 (2) 操作可以二分。

应该是(nlog^2n)带个(26)常数,跑的贼慢

#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 2e5 + 100;
struct BIT{
	int f[maxn];
	inline int lowbit(int x){return x & (-x);}
	inline void add(int pos,int x){
		while(pos < maxn){
			f[pos] += x;
			pos += lowbit(pos);
		}
	}
	inline int query(int pos){
		int res = 0;
		while(pos){
			res += f[pos];
			pos -= lowbit(pos);
		}
		return res;
	}
};
struct node{
	int val[maxn],beg = 1,end = 0;
	BIT bit;
	inline int top(){return val[beg] + bit.query(beg);}
	inline int query(int pos){return val[pos] + bit.query(pos);}
	inline void push(int x){
		val[++end] = x;
	}
	inline void pop(){
		beg++;
	}
	inline void modify(int limit){
		int l = beg,r = end,ans = -1;
		if(!end)return;
		while(l <= r){
			int mid = (l + r) >> 1;
			if(bit.query(mid) + val[mid] < limit)ans = mid,l = mid + 1;
			else r = mid - 1;
		}
		if(ans == -1)return;
		bit.add(beg,1);
		bit.add(ans + 1,-1);
	}
}val[26];
inline int idx(char c){return c - 'a';}
int n;
long long ans;
string str,rev;
int main(){
	ios::sync_with_stdio(false);
	cin >> n;
	cin >> str;
	rev = str;
	reverse(rev.begin(),rev.end());
	for(int i = 0;i < n;i++)
		val[idx(str[i])].push(i);
	for(int i = 0;i < n;i++){
		int pos = val[idx(rev[i])].top();
		ans += pos - i;
		val[idx(rev[i])].pop();
		for(int j = 0;j < 26;j++)
			if(j != idx(rev[i]))val[j].modify(pos);
	}
	cout << ans << '
';
	return 0;
}

有个更妙的做法,从逆序对的角度来考虑(可能我还是太naive了)

假设原串每个字符的编号为 (1-n),然后我们将反串每个字符的位置按升序求出来,我们就求出了最优情况下每个字符应该变换到的位置,比如第一个样例aaaza对应的序列应该是 (1,3,4,2,5),交换相邻两数,使得数列有序,这个十分经典,答案即为这个序列的逆序对

#include <cstdio>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 2e5 + 100;
namespace bit{
	int f[maxn];
	inline int lowbit(int x){return x & (-x);}
	inline void add(int pos,int x){
		while(pos < maxn){
			f[pos] += x;
			pos += lowbit(pos);
		}
	}
	inline int query(int pos){
		int res = 0;
		while(pos){
			res += f[pos];
			pos -= lowbit(pos);
		}
		return res;
	}
}
inline int idx(char c){return c - 'a';}
vector<int> vec[26];
int n,val[maxn],beg[maxn];
long long ans;
char str[maxn],rev[maxn];
int main(){
	scanf("%d",&n);
	scanf("%s",str + 1);
	for(int i = 1;i <= n;i++)rev[i] = str[i];
	reverse(rev + 1,rev + 1 + n);
	for(int i = 1;i <= n;i++)
		vec[idx(rev[i])].push_back(i);
	for(int i = 1;i <= n;i++)
		val[i] = vec[idx(str[i])][beg[idx(str[i])]++];
	for(int i = n;i >= 1;i--)
		ans += bit::query(val[i]),bit::add(val[i],1);
	printf("%lld
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/colazcy/p/13861692.html