寿司
题目描述
解析
合法的结果只有两种情况:\(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;
}