寿司
寿司
题目描述
解析
合法的结果只有两种情况:\(B\) 都在两边、\(R\) 都在两边,至于是最左边还是最右边或者都有,无所谓,因为是环。
而每个 \(B\) 移到最左边的代价就是它左边 \(R\) 的个数,移到最右边就是它右边 \(R\) 的个数。
按环形 dp 的套路,我们可以把串复制二倍,然后枚举断点,破环成链,然后对于每个链把 \(B\) 移到区间两边。
因为我们已经遍历每一个断点,所以不用考虑 \(R\) 在断点处的情况,只用考虑 \(B\)。
这是暴力版的 \(O(n^2)\):
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
int t,n,_1[N],_0[N];
string s;
int main()
{
freopen("sushi.in","r",stdin);
freopen("sushi.out","w",stdout);
scanf("%d",&t);
while(t--)
{
cin>>s; int n=s.length();
s=' '+s+s;
for(int i=1;i<=n<<1;i++)
{
_1[i]=_1[i-1]+(s[i]=='B');
_0[i]=_0[i-1]+(s[i]=='R');
}
long long ans=1e18;
for(int l=1,r=n;r<=n<<1;l++,r++)
{
long long res=0;
for(int i=l;i<=r;i++)
{
if(s[i]=='B')
res+=min(_0[i]-_0[l-1],_0[r]-_0[i]);
}
ans=min(ans,res);
}
printf("%lld\n",ans);
}
return 0;
}
然后我们考虑优化,每一个序列一定存在一个位置,它左边的 \(B\) 一定移到最左边最优,右边的 \(B\) 一定移到右边最优。
并且随着我们的区间右移的过程中,这个位置一定是单调的(易证),所以我们可以单调指针指一下。
这对我们优化有什么帮助呢?我们还是先写出暴力:
for(int l=1,r=n;r<=n<<1;l++,r++)
{
long long res=0;
while(_0[tag]-_0[l-1]<_0[r]-_0[tag]) tag++;
for(int i=l;i<=tag;i++) if(s[i]=='B') res+=_0[i]-_0[l-1];
for(int i=tag+1;i<=r;i++) if(s[i]=='B') res+=_0[r]-_0[i];
ans=min(ans,res);
}
我们发现可以前缀和维护一下!
然后做完了。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6+5;
int t,n,_1[N],_0[N];
long long sum[N];
char s[N];
int main()
{
freopen("sushi.in","r",stdin);
freopen("sushi.out","w",stdout);
scanf("%d",&t);
while(t--)
{
scanf("%s",s+1); n=strlen(s+1);
for(int i=1;i<=n;i++) s[i+n]=s[i];
for(int i=1;i<=n<<1;i++)
{
_1[i]=_1[i-1]+(s[i]=='B');
_0[i]=_0[i-1]+(s[i]=='R');
if(s[i]=='B') sum[i]=sum[i-1]+_0[i];
else sum[i]=sum[i-1];
}
long long ans=1e18; int tag=1;
for(int l=1,r=n;r<=n<<1;l++,r++)
{
long long res=0;
while(_0[tag]-_0[l-1]<_0[r]-_0[tag]) tag++;
res+=sum[tag]-sum[l-1]-(long long)_0[l-1]*(_1[tag]-_1[l-1]);
res+=(long long)(_1[r]-_1[tag])*_0[r]-sum[r]+sum[tag];
ans=min(ans,res);
}
printf("%lld\n",ans);
}
return 0;
}