【树状数组】POI2012 Letters

说实话
题目其实不难
需要你静下心来想
—— 一只蒟蒻的想法╮(╯▽╰)╭


【题目描述】

给出两个长度相同且由大写英文字母组成的字符串A、B,保证A和B中每种字母出现的次数相同。
现在每次可以交换A中相邻两个字符,求最少需要交换多少次可以使得A变成B。

【输入格式】

第一行一个正整数 n ( 2 &lt; = n &lt; = 1 , 000 , 000 ) n (2&lt;=n&lt;=1,000,000) n(2<=n<=1,000,000),表示字符串的长度。
第二行和第三行各一个长度为 n n n 的字符串,并且只包含大写英文字母。

【输出格式】

一行,一个整数,表示答案。

【样例输入输出】

样例输入

3
ABC
BCA

样例输出

2

样例解释:ABC -> BAC -> BCA

【数据规模与约定】

时间限制: 1 s 1s 1s
空间限制: 256 M B 256MB 256MB
【解析】
显然,求最小交换次数就是求逆序对的个数 至于证明,感兴趣的同学可以上网查找
可能不太懂,没关系,我们举个例子。
假设: A : A B C B C C A B : B C C B C A A A:\qquad A\qquad B\qquad C\qquad B\qquad C\qquad C\qquad A\\ B:\qquad B\qquad C\qquad C\qquad B\qquad C\qquad A\qquad A A:ABCBCCAB:BCCBCAA先将 A A A 中的字母按升序标号:
在这里插入图片描述
然后,我们将其与B一一配对:因为交换次数要最小,所以肯定倒着找从A中对应的B中左边最近的配对,若没有,则正着找右边最近的(嗯 似乎不太好理解,没关系,下面会详解)

那么,我们需要知道如何求出B中各个字母原来在A序列中的位置。
因此,开一个数组 p o s [ I ] pos[I] pos[I] 记录与字母 I I I 相等的最近的字母 J J J J &lt; I J&lt;I J<I 的位置,先扫描A序列,将其存在一个升序的数组 f [ i ] f[i] f[i] 中 ,接着倒叙扫描B序列,通过 f [ i ] f[i] f[i] 传递找到在A序列中的位置,储存到一个 s [ i ] s[i] s[i] 数组中,然后,根据之前的思路,用树状数组对 s [ i ] s[i] s[i] 求逆序对,即为所求。
【核心代码】

	for(int i=0;i<n;++i)
	{
		f[i+1]=pos[s1[i]-'A'+1];
		pos[s1[i]-'A'+1]=i+1;
	}
	for(int i=n-1;i>=0;--i)
	{
		s[i+1]=pos[s2[i]-'A'+1];
		pos[s2[i]-'A'+1]=f[pos[s2[i]-'A'+1]];
	}

如果还不能很理解,我们就这上面的样例模拟一遍:
1、第一个循环

i0123456
f[i]f[1]=pos[A]=0f[2]=pos[B]=0f[3]=pos[C]=0f[4]=pos[B]=2f[5]=pos[C]=3f[6]=pos[C]=5f[7]=pos[A]=1
pos[I]pos[A]=1pos[B]=2pos[C]=3pos[B]=4pos[C]=5pos[C]=6pos[A]=7

注解:第一次循环后,数组的值: f [ 1 ] = 0 , f [ 2 ] = 0 , f [ 3 ] = 0 , f [ 4 ] = 2 , f [ 5 ] = 3 , f [ 6 ] = 5 , f [ 7 ] = 1 p o s [ A ] = 7 , p o s [ B ] = 4 , p o s [ C ] = 6 \begin{aligned}&amp; f[1]=0,f[2]=0,f[3]=0,f[4]=2,f[5]=3,f[6]=5,f[7]=1\\ &amp;pos[A]=7,pos[B]=4,pos[C]=6\end{aligned} f[1]=0,f[2]=0,f[3]=0,f[4]=2,f[5]=3,f[6]=5,f[7]=1pos[A]=7,pos[B]=4,pos[C]=6
2、第二个循环

i6543210
s[i]s[7]=pos[A]=7s[6]=pos[A]=1s[5]=pos[C]=6s[4]=pos[B]=4s[3]=pos[C]=5s[2]=pos[C]=3s[1]=pos[B]=2
pos[I]pos[A]=f[pos[A]]=f[7]=1pos[A]=f[pos[A]]=f[1]=0pos[C]=f[pos[C]]=f[6]=5pos[B]=f[pos[B]]=f[4]=2pos[C]=f[pos[C]]=f[5]=3pos[C]=f[pos[C]]=f[3]=0pos[B]=f[pos[B]]=f[2]=0

注解:第二次循环后,数组的值: s [ 1 ] = 2 , s [ 2 ] = 3 , s [ 3 ] = 5 , s [ 4 ] = 4 , s [ 5 ] = 6 , s [ 6 ] = 1 , s [ 7 ] = 7 p o s [ A ] = 0 , p o s [ B ] = 0 , p o s [ C ] = 0 \begin{aligned} &amp;s[1]=2,s[2]=3,s[3]=5,s[4]=4,s[5]=6,s[6]=1,s[7]=7\\ &amp;pos[A]=0,pos[B]=0,pos[C]=0\end{aligned} s[1]=2,s[2]=3,s[3]=5,s[4]=4,s[5]=6,s[6]=1,s[7]=7pos[A]=0,pos[B]=0,pos[C]=0A序列中每个字母在B序列中的位置如下:
在这里插入图片描述
对应关系即为

最后我们只要建立 s [ i ] s[i] s[i] 的树状数组求逆序对个数即可。
【代码展示】

#include<bits/stdc++.h>
#define ud using namespace std
#define ll long long
ud;
const int maxn=1e6+1000;
int n;
string s1,s2;
int c[maxn],f[maxn],pos[maxn],s[maxn];
inline long long read()
{
	long long sum=0,flag=1;
	char c;
	for(;c<'0'||c>'9';c=getchar())if(c=='-') flag=-1;
	for(;c>='0'&&c<='9';c=getchar())sum=(sum<<1)+(sum<<3)+c-'0';
	return sum*flag;
}
void add(int x,int y)
{
	for(;x<=n;x+=x&(-x))
	c[x]+=y;
}
int ask(int x)
{
	int ans=0;
	for(;x;x-=x&(-x))
	ans+=c[x];
	return ans;
}
int main()
{
	n=read();
	cin>>s1;
	cin>>s2;
	for(int i=0;i<n;++i)
	{
		f[i+1]=pos[s1[i]-'A'+1];
		pos[s1[i]-'A'+1]=i+1;
	}
	for(int i=n-1;i>=0;--i)
	{
		s[i+1]=pos[s2[i]-'A'+1];
		pos[s2[i]-'A'+1]=f[pos[s2[i]-'A'+1]];
	}
	ll ans=0;
	for(int i=1;i<=n;++i)//也可以倒序求逆序对
	{
		ans+=ask(n)-ask(s[i]);
		add(s[i],1);
	}
	printf("%lld\n",ans);
	return 0;
}

好了,这次的讲解就到这里,大家如果有点累了,可以放松一下

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值