CF1616E Lexicographically Small Enough

传送门

在此纪念考试时忘开 long long 的我。

题意

给你两个字符串,每次可以交换字符串一中相邻的两个字符,请你求出最少交换几次可以使字符串一比字符串二的字典序更小。

思路

贪心部分

贪心,每次分为以下两种情况。

  • 在此位置确定字符串一的字典序更小。

  • 在此位置以后确定字符串一的字典序更小。

什么意思呢?

举个例子,假设两个字符串分别为:

qaq
qwq

如果我们要在第一个字符时确定字符串一的字典序更小,必须使字符串一的第一个字符比字符串二的第一个字符的字典序更小。

在这两个字符串中,满足条件的字符串一只有:

aqq

如果我们要在第二个字符时确定字符串一的字典序更小,必须使字符串一的第二个字符比字符串二的第二个字符的字典序更小。

除此之外,还必须保证之前已改变的字符串一致,如果不一致,字典序就会不同,后面的字符也就不需要再管了。

在这两个字符串中,满足条件的字符串一只有:

qaq

所以我们只需要找到距离此处最近,且要么目前的字符串一比目前的字符串二的字典序小,要么目前的字符串一与目前的字符串二的字典序相等的情况即可。

只要找到此处字典序更小的情况,就可以记一次答案,而在前面的字符相等的情况下,只需要找出后来字符串一的某个字符的字典序更小的情况再加上前面的字符相等的情况的最小交换数即可。

其他部分

接下来,我们要考虑在交换过程中,后面的字符向前移动,可以看做前面交换过的字符在做相对运动,即向后移动一位。

所以,对于每次移动(指后面的字符移动到前面的某个位置,也就是进行多次交换),只需要让在它前面且在它移动到的位置的后面的所有字符的位置加上一即可。

此处可以用求和的方式,找到每一次移动前的最初始位置(刚输入时的位置),并将这个位置的值变为一,前面每一个字符串的位置就是从目前位置的值加到最后一个位置的值。

此处可以用分块或线段树等多种方式求出和,本人的代码就是用的分块。

时间复杂度为 O ( n n ) O(n \sqrt n) O(nn )

AC 代码

#include<bits/stdc++.h>
using namespace std;
char c1[100005],c2[100005];
long long n,sum[30],w[30][100005],u[30],now=0,ans=1e12,h[100005],k[100005],kl=0,ks=0;
int qh(int x)
{
	long long hz=0,nks=(x-1)/kl+1;
	for(int i=x;i<=min(nks*kl,n);i++)
	{
		hz+=h[i];
	}
	for(int i=nks+1;i<=ks;i++)
	{
		hz+=k[i];
	}
	return hz;
}
void become(int x)
{
	for(int i=1;i<=ks;i++)
	{
		if(((i-1)*kl)+1<=x&&(i*kl)>=x)
		{
			k[i]++;
			break;
		}
	}
	return;
}
signed main()
{
	int t;
	cin>>t;
	while(t--)
	{
		ans=1e12;
		cin>>n;
		kl=sqrt(n);
		if(n%kl==0)
		{
			ks=n/kl;
		}
		else
		{
			ks=n/kl+1;
		}
		for(int i=1;i<=n;i++)
		{
			cin>>c1[i];
		}
		for(int i=1;i<=ks;i++)
		{
			k[i]=0;
		}
		long long g=100000;
		for(int i=1;i<=min(ks*kl,g);i++)
		{
			h[i]=0;
		}
		for(int i=1;i<=n;i++)
		{
			cin>>c2[i];
		}
		for(int i=0;i<=25;i++)
		{
			sum[i]=0;
			u[i]=0;
			for(int j=1;j<=n;j++)
			{
				w[i][j]=-1;
			}
		}
		for(int i=1;i<=n;i++)
		{
			sum[int(c1[i]-'a')]++;
			w[int(c1[i]-'a')][sum[int(c1[i]-'a')]]=i;
		}
		now=0;
		for(int i=1;i<=n;i++)
		{
			if(w[int(c2[i]-'a')][u[int(c2[i]-'a')]+1]==-1)
			{
				for(int j=0;j<c2[i]-'a';j++)
				{
					if(w[j][u[j]+1]!=-1)
					{
						ans=min(ans,now+abs(i-(w[j][u[j]+1]+qh(w[j][u[j]+1]))));
					}
				}
				break;
			}
			else
			{
				for(int j=0;j<c2[i]-'a';j++)
				{
					if(w[j][u[j]+1]!=-1)
					{
						ans=min(ans,now+abs(i-(w[j][u[j]+1]+qh(w[j][u[j]+1]))));
					}
				}
				u[int(c2[i]-'a')]++;
				now+=abs(i-(w[int(c2[i]-'a')][u[int(c2[i]-'a')]]+qh(w[int(c2[i]-'a')][u[int(c2[i]-'a')]])));
				h[w[int(c2[i]-'a')][u[int(c2[i]-'a')]]]++;
				become(w[int(c2[i]-'a')][u[int(c2[i]-'a')]]);
			}
		}
		if(ans==1e12)
		{
			cout<<-1<<endl;
		}
		else
		{
			cout<<ans<<endl;
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值