洛谷题单指南-前缀和差分与离散化-P2882 [USACO07MAR] Face The Right Way G

原题链接:https://www.luogu.com.cn/problem/P2882

题意解读:一个有F、B组成的序列,每次可以翻转k个连续子序列,翻转:F->B,B->F,计算最少翻转多少次可以将序列都变成F,并求相应的k。

解题思路:

为方便处理,设F为1,B为0

1、朴素做法

枚举k:1~n

  枚举序列,一旦遇到0,就将连续k个字符翻转,如果可翻转的字符不足k个,则结束,这里也是贪心的思想,既然要全部变成F,当然遇到B就开始翻转会比较合理,这样已经处理过的位置就都变成F了,不用回头去处理。

时间复杂度:枚举k是n,枚举序列是n,将k个字符翻转是n,一共n^3,需要考虑优化

80分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 5005;
int a[N], b[N];
int n, ansk, ansm = INT_MAX;

int main()
{
    cin >> n;
    char c;
    for(int i = 1; i <= n; i++)
    {
        cin >> c;
        if(c == 'F') a[i] = 1;
        else if(c == 'B') a[i] = 0;
    }

    for(int k = 1; k <= n; k++)
    {
        memcpy(b, a, sizeof(a)); //把a拷贝一份
        int m = 0;
        for(int i = 1; i <= n; i++)
        {
            if(b[i] == 0)
            {
                int end = i + k - 1;
                if(end > n) break;
                //翻转操作
                for(int j = i; j <= end; j++)
                {
                    b[j] ^= 1; 
                }
                m++;
            }
            //如果i走到最后,且最后一个已经变为F
            if(i == n && b[i] == 1)
            {
                if(ansm > m)
                {
                    ansm = m;
                    ansk = k;
                }
            }
        } 
    }
    cout << ansk << " " << ansm;
    return 0;
}

2、差分优化

 TLE的关键在于翻转操作需要枚举k个元素,一个一个处理,但实际上,并不需要真的做翻转,只需要做一个标记:

bool flag=true表示从当前的i位置往后执行翻转,f[i + k]=true表示从i + k的位置执行翻转,因为做两次翻转等于不变,因此这样的效果就是标记了

从i到i-k+1范围执行翻转。

这样一来,在判断a[i]的数值时,可以结合flag来计算翻转之后的值。

要注意flag和f[]的几个更新点:

当要翻转时,flag = flag ^ 1,f[i + k] ^= 1

当遇到f[i]=true时,flag = flag ^ 1

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 5005;
int a[N], b[N];
int n, ansk, ansm = INT_MAX;

int main()
{
    cin >> n;
    char c;
    for(int i = 1; i <= n; i++)
    {
        cin >> c;
        if(c == 'F') a[i] = 1;
        else if(c == 'B') a[i] = 0;
    }

    for(int k = 1; k <= n; k++)
    {
        memcpy(b, a, sizeof(a)); //把a拷贝一份
        int m = 0;
        bool flag = false; //当前是否翻转
        bool f[N] = {false}; //f[i]=true表示从i之后是否翻转
        for(int i = 1; i <= n; i++)
        {
            if(f[i] == true) flag ^= 1; //当前状态改变
            if((b[i] ^ flag) == 0)
            {
                if(i + k - 1 > n) break;
                //翻转操作
                flag ^= 1; //当前状态基础上再翻转
                f[i + k] ^= 1; //i+k之后翻转
                m++; 
            }
            //如果i走到最后,且最后一个已经变为F
            if(i == n && (b[i] ^ flag) == 1)
            {
                if(ansm > m)
                {
                    ansm = m;
                    ansk = k;
                }
            }
        }
    }
    cout << ansk << " " << ansm;
    return 0;
}

 

posted @ 2024-08-01 12:08  五月江城  阅读(27)  评论(0编辑  收藏  举报