寿司
大体意思是让字符串的两种字符串分开。首先,可想到处理环的问题,转换成2*n-1的序列问题。
手玩的几个性质:
1.最优的方案一定只有不同颜色的寿司交换
2.一个寿司的移动步数可以O(1)求
3.一定存在一个断点使得所有蓝色的移向两侧(假设动蓝色的)(和全都移向断点一样)
4.只考虑蓝色的和只考虑红色的效果一样
40%
我们为了确保所有的方案都能统计到,先枚举断点,再枚举这种情况下的左端点。(起初,并没有理解为什么要枚举左端点,其实,是因为断点是移项中不会经过的点,但所谓的环断成链以后,不能确保最优的聚合的中间点是谁)。O(n^2)
100%
(learn from xm大神)
设左端点为断点,中间点为zh
能发现40%算法的问题在于要枚举的情况有点多,考虑一种性质:一个B移动的最优时,一定只和R交换,所以他的移动步数为左/右取min。再往深的一层说,一个固定的序列,你会发现随着下标递增,B左边的R数在增多,右边的R数在减少。(显然有单调性)B肯定往R少的一侧去,那么当枚举到一个l时,zh就可以二分查找了。
emmm。。。然而还不够,在这种优秀的O(n*logn)下还不能O(1)查询一种情况的贡献。
此时,前缀和思想可以实现(不是普通的前缀和)
设qsm[i]为前缀R的个数,qqsm[i]为将1~i的B点全部移到最左边的代价。
那么将zh到l的点移到左边的代价为
qqsm[zh]-qqsm[l-1]-qsm[l-1]*(zh-l+1-qsm[zh]+qsm[l-1])
解释一下:简单的qqsm[zh]-qqsm[l-1]并不对,为什么?考虑此时只是将l~zh的B点移动到了最左边,而不能保证,移动到以l为界限的左边,此时l~zh中每一个B会多走1~l-1中R的个数步,也要减去。
最后:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
#define MAXN 2000010
#define ll long long
#define INF 0x7ffffffffff
using namespace std;
ll T,len;
char rn[MAXN];
ll ans;
ll cnt;
double xian;
ll qsm[MAXN],hsm[MAXN];
ll qqsm[MAXN],hhsm[MAXN];
inline ll minn(ll a,ll b){
return a<b?a:b;
}
ll erfen(ll l,ll r,ll bi){
if(l==r)return l;
ll mid=l+r>>1;
if(qsm[mid]-qsm[bi-1]>xian)
return erfen(l,mid,bi);
else if(qsm[mid]-qsm[bi-1]<xian)
return erfen(mid+1,r,bi);
else
return mid;
}
ll cal(ll l,ll zh,ll r){
ll res=0;
res+=qqsm[zh]-qqsm[l-1]-qsm[l-1]*(zh-l+1-qsm[zh]+qsm[l-1]);
res+=hhsm[zh+1]-hhsm[r+1]-hsm[r+1]*(r-zh-hsm[zh+1]+hsm[r+1]);
return res;
}
int main(){
// freopen("da.in","r",stdin);
scanf("%lld",&T);
while(T){
--T;
ans=INF;
scanf("%s",rn+1);
len=strlen(rn+1);
cnt=0;
qqsm[0]=qsm[0]=0;
hhsm[2*len]=hsm[2*len]=0;
ll lim=2*len-1;
for(int i=1;i<len;++i)
rn[i+len]=rn[i];
for(ll i=1;i<=lim;++i){
qsm[i]=qsm[i-1]+(rn[i]=='B');
if(i<=len)cnt+=(rn[i]=='B');
if(rn[i]=='R')
qqsm[i]=qqsm[i-1]+qsm[i-1];
else
qqsm[i]=qqsm[i-1];
}
xian=cnt*0.5;
for(ll i=lim;i>=1;--i){
hsm[i]=qsm[lim]-qsm[i-1];
if(rn[i]=='R')
hhsm[i]=hhsm[i+1]+hsm[i+1];
else
hhsm[i]=hhsm[i+1];
}
/*for(int i=1;i<=lim;++i){
printf("i=%d %lld %lld %lld %lld\n",i,qsm[i],qqsm[i],hsm[i],hhsm[i]);
}*/
ll zh;
for(ll l=1;l<=len;++l){
zh=erfen(l,l+len-1,l);
//cout<<l<<" "<<zh<<" "<<l+len-1<<endl;
ans=minn(ans,cal(l,zh,l+len-1));
//cout<<cal(l,zh,l+len-1)<<endl;
}
printf("%lld\n",ans);
}
}
/*
R到左边的步数为左边B的个数
*/
感觉决策单调性和O(1)查询都挺神的。