亂搞 壽司

題面去內網找。。
第一思路當然是找一個位置成爲斷點,讓所有的移動都不經過它,讓一部分點到這個斷點的兩側。很明顯是有符合方案的,並且在所有枚舉中有一種是最優解。
於是我考試時打了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);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章