欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[Noip模拟题]寿司

程序员文章站 2023-12-26 10:03:15
...

Description
小 c 是一名 oier。最近,他发现他的数据结构好像学傻了。因为他在刷题时碰到了一道傻逼数据结构题,强行使用了平衡树来解决,卡着时间 AC。为此,他被狠狠地嘲讽了一番。于是,小 c 找了大量的数据结构题来做。昨天,小 c 正在吃寿司,突然发现许多盘寿司围成了一个圆圈,这些寿司中有红色的也有蓝色的。由于小 c 看交错的颜色非常不爽,想通过一些操作,使得所有的红色寿司形成了一块连续的区域,蓝色的寿司也形成了一块连续的区域。如果小 c 每次只可以交换相邻的两盘寿司,那么最少需要多少步才可以达到小 c 的要求呢?由于他做题做多了,脑袋已经有点不清醒了,于是这个问题就交给你了。

Input
第一行一个数 TT<=10表示数据组数。
接下来 T 行,每行一行由 B 和 R 组成的字符串,长度<=1000000,B 表示蓝色, R 表示红色。第 i 个字符描述顺时针数第 i 盘寿司的颜色。注意,最后一盘寿司和第 1 盘寿司是相邻的。

Output
对于每组数据,输出一行表示最小的交换次数。

Sample Input
1
BBRBBRBBBRRR

Sample Output
5

HINT
Sol

标准题解请点击这里

自己的题解(part 1)
上面的标准题解我几乎没有看懂……
然后就开始乱搞。
首先,把样例画出来:
[Noip模拟题]寿司
可以看出,选择一个点,将与它同颜色的点向它靠近,这样是较优的方案。
而向它靠近的方式,显然是在它左侧的点靠近后在它左侧,在它右侧的点靠近后也在它的右侧。
那么时间复杂度为O(tlen2)的算法就出来了:
枚举每一个中心点,然后枚举其他与它同颜色的点是向它左侧靠还是向它右侧靠近。

O(tlen2)代码

#include <cstdio>
#include <cstring>

const int maxn=1000000;
const int inf=2000000000;

int t,len;
char s[maxn+10];

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%s",s);
        len=strlen(s);
        int ans=inf;
        for(int i=0; i<len; i++)
        //枚举中心点
        {
            int sum=0;
            int nowl=i,cntl=0;
            int nowr=i,cntr=0;
            for(int j=1; j<=(len+1)/2-1; j++)
            //寻找在中心点左侧的点
            {
                nowl--;
                int now=nowl;
                if(now<0)
                {
                    now=(now+len)%len;
                }
                if(s[now]==s[i])
                {
                    cntl++;
                    sum+=i-nowl-cntl;
                }
            }
            for(int j=1; j<=(len+1)/2-1; j++)
            //枚举在中心点右侧的点
            {
                nowr++;
                int now=nowr;
                if(now>=len)
                {
                    now%=len;
                }
                if(s[now]==s[i])
                {
                    cntr++;
                    sum+=nowr-i-cntr;
                }
            }
            if(!(len&1))
            //讨论正对中心点的点
            {
                if(s[i]==s[(i+len/2)%len])
                {
                    if(cntl>cntr)
                    {
                        cntl++;
                        sum+=len/2-cntl;
                    }
                    else
                    {
                        cntr++;
                        sum+=len/2-cntr;
                    }
                }
            }
            if(sum<ans)
            {
                ans=sum;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

自己的题解(part 2)
看了上面的思路,我们可以发现,每个中心点左侧和右侧的点的数量(包括红色点和蓝色点)都是相同的,而中心点之间又有很多重复之处,那么我们可以想到优化方法了:队列。预处理,用两个队列存储每个点作为中心点时,左侧或右侧的点区要想使某种颜色集中在某一侧,所需要花费的代价。那么每个点就使用已经算出来的数据在O(1)的时间复杂度内得出解,总时间复杂度O(tlen)

O(tlen)代码

#include <cstdio>
#include <cstring>

const int maxn=1000000;
const long long inf=200000000000;

int t,len,a[maxn+10];
long long f[2][2][maxn+10];
long long cnt[2][2][maxn+10];
char s[maxn+10];

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        memset(f,0,sizeof f);
        memset(cnt,0,sizeof cnt);
        scanf("%s",s);
        len=strlen(s);
        for(int i=0; i<len; i++)
        {
            if(s[i]=='R')
            {
                a[i]=0;
            }
            else
            {
                a[i]=1;
            }
        }
        long long ans=inf;
        for(int i=0; i<=(len+1)/2-2; i++)
        //首先处理(len-1)号点作中心点时移动左侧的点的花费
        {
            cnt[0][a[i]][0]++;
            f[0][a[i]][0]+=i-cnt[0][a[i]][0]+1;
        }
        for(int i=1; i<len; i++)
        //再用队列优化处理0~(len-2)作中心点时左侧点的花费的方式
        {
            cnt[0][0][i]=cnt[0][0][i-1];
            cnt[0][1][i]=cnt[0][1][i-1];
            cnt[0][a[i-1]][i]--;
            f[0][0][i]=f[0][0][i-1];
            f[0][1][i]=f[0][1][i-1];
            f[0][1-a[i-1]][i]-=cnt[0][1-a[i-1]][i];
            cnt[0][a[(i+(len+1)/2-2)%len]][i]++;
            f[0][a[(i+(len+1)/2-2)%len]][i]+=(len+1)/2-1-cnt[0][a[(i+(len+1)/2-2)%len]][i];
        }
        for(int i=len-1; i>=len-(len-1)/2; i--)
        //处理出0号点作中心点时移动右侧的点花费的代价
        {
            cnt[1][a[i]][len-1]++;
            f[1][a[i]][len-1]+=len-i-cnt[1][a[i]][len-1];
        }
        for(int i=len-2; i>=0; i--)
        //再用队列优化处理1~(len-1)作中心点时移动右侧点的计算方式
        {
            cnt[1][0][i]=cnt[1][0][i+1];
            cnt[1][1][i]=cnt[1][1][i+1];
            cnt[1][a[i+1]][i]--;
            f[1][0][i]=f[1][0][i+1];
            f[1][1][i]=f[1][1][i+1];
            f[1][1-a[i+1]][i]-=cnt[1][1-a[i+1]][i];
            cnt[1][a[(i-(len+1)/2+2+len)%len]][i]++;
            f[1][a[(i-(len+1)/2+2+len)%len]][i]+=(len+1)/2-1-cnt[1][a[(i-(len+1)/2+2+len)%len]][i];
        }
        for(int i=0; i<len; i++)
        //枚举每个点作中心点时的情况
        {
            long long sum=f[0][a[i]][(i+1)%len]+f[1][a[i]][(i-1+len)%len];
            //sum就是左侧的点与右侧点向中心点移动花费的距离之和
            if(!(len&1))
            //最后讨论正对中心点的点
            {
                if(a[i]==a[(i+len/2)%len])
                {
                    if(cnt[0][a[i]][(i+1)%len]>cnt[1][a[i]][(i-1+len)%len])
                    {
                        sum+=len/2-cnt[0][a[i]][(i+1)%len];
                    }
                    else
                    {
                        sum+=len/2-cnt[1][a[i]][(i-1+len)%len];
                    }
                }
            }
            if(sum<ans)
            //更新ans
            {
                ans=sum;
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}

上一篇:

下一篇: