乱搞 寿司

题面去内网找。。
第一思路当然是找一个位置成为断点,让所有的移动都不经过它,让一部分点到这个断点的两侧。很明显是有符合方案的,并且在所有枚举中有一种是最优解。
于是我考试时打了O(N^2)的暴力。。。。枚举每一个点是要移动到左边还是右边。而实际上是可以找到一个边界之后用前缀和,找边界可以二分。O(N*logN),如果数据水可以卡过去。
但是,如果移动红点,枚举每一个蓝点作为断点,边界就是最中间一个蓝点的位置(在这里左右蓝点数一样,向左一个红点就一定向左移动比较优,向右同理)。而断点向右移动一个,边界也会移动一个蓝点,那么二分干嘛。。直接找就好了。
以蓝点记录区间里红点的个数,拉成链(可以考虑把最后一个蓝点搞成第0个蓝点),然后搞个前缀和。维护一个sum的值表示搞到这里的答案,找到边界后,因为是断点右移,那么边界以左的红点走的步数-1,以右的+1.因为有红点的前缀和,搞起来就很容易了。
还有一个坑,边界右移后会越过一些红点,导致它们的答案压根不会发生改变。但是如果蓝点是偶数个,位于两边界间的红点其实是在真正的边界上(最中间应该是两蓝点的中间部分,两蓝点都在边界两侧),这些红点对于边界在他左边一个和在他右边一个都是一样的。但是如果有奇数个蓝点就不太一样了,就要减去多出来的(也就是当他在边界以右,而实际上越到边界左侧的红点,他们对答案并没产生额外贡献)

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <cstring>
#define ll long long
#define N 1001005
using namespace std;
int t,n;char s[N];ll b,r,ans,now,a[N*2];
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%s",s+1);n=strlen(s+1);b=r=now=ans=0;
        memset(a,0,sizeof(a));
        for(int i=1;i<=n;i++)
            if(s[i]=='R')r++,a[b]++;
            else b++;
        for(ll i=1;i<b;i++)now+=min(i,b-i)*a[i];
        ans=now;a[0]+=a[b];
        for(int i=b;i<b*2;i++)a[i]=a[i-b];
        for(int i=1;i<b*2;i++)a[i]+=a[i-1];
        for(int i=1;i<b;i++)
        {
            ll l=a[i+(b>>1)-1]-a[i-1];now-=l;now+=r-l;
            if(b&1)now-=a[i+(b>>1)]-a[i+(b>>1)-1];
            ans=min(ans,now);
        }
        printf("%lld\n",ans);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章