POJ 3276 Face The Right Way 反转

一、题目大意

有N(1<=N<=1e5)头牛,它们有一个方向F或者B,我们要让它们方向都变为F,给定一个机器,可以把k头牛(1<=k<=n)的方向取反(取反两次等于之前),求出合适的k,最小化取反次数m,输出m相同且最小时候的k。

注意本题目每次一定要反转k头牛,假如说有5头牛,第1头方向反了,k=3,我们一定要反转第3头牛来调整它的方向。(题目中的意思是,每次不能反转少于k头牛,例如在队列的两端,这个我英文不好一开始没理解,以为边缘可以特殊考虑,错了好多次)

二、解题思路

题目需要遍历所有可能的k,求出结果然后不断优化,重点在于如何在O(n)时间内求出k的反转次数。

我们每次只考虑最左边的这头牛i,如果i的方向靠前,那就不用考虑i,只需要处理[i+1,N),如果它方向靠后,我们一定要反转[i,i+k-1]这些牛,因为无法对少于k头牛反转,所以必须以i为起点。然后反转k头牛不需要真正的反转,只需要记录一个数组f[i],用来代表[i,i+k-1]这段区间内是否有过反转即可,

然后对于每一头牛,我们只需要通过 f[i-k+1],f[i-k+2]...f[i-1],和一开始牛的方向,即可直到它现在的方向,(用尺取法去维护每个元素的前k-1项的和是O(1)),所以本题目的复杂性为O(n^2)

最后计算反转次数,只需要对f[i]数组求和即可

需要注意的是,我们不能对少于k头牛进行反转,那么如果数组f[i]在i∈[n-k+2,n]有任意f[i]==1,那么这组解都无效。

我当时出于放心考虑,所以按照从左到右的思想(只是变为了i=n;i--;i>=1,维护的值为后k-1项的和,最后判可行性的时候判的是[1,k-1]),又针对每个k从右到左计算了一次,貌似可以不用算从右到左,但我没理解为社么,不过时间上这个也是可以过的

三、代码

#include 
using namespace std;
bool front[5007];
int revCnt[5007], n, ansk, ansm, k;
void input()
{
    scanf("%d", &n);
    char c;
    for (int i = 1; i <= n; i++)
    {
        getchar();
        scanf("%c", &c);
        if (c == 'F')
        {
            front[i] = true;
        }
        else
        {
            front[i] = false;
        }
    }
}
void flushRevCnt()
{
    for (int i = 0; i <= n; i++)
    {
        revCnt[i] = 0;
    }
}
// 从左到右
void solveRighter()
{
    int beforeKRevCnt = 0;
    // 从左到右边考虑,[i-k+1,i-1]反转的数量会影响到i的位置
    for (int i = 1; i <= n; i++)
    {
        bool isFront = front[i];
        if (beforeKRevCnt % 2 != 0)
        {
            isFront = !front[i];
        }
        if (!isFront)
        {
            revCnt[i] = 1;
        }
        beforeKRevCnt += revCnt[i];
        if (i - k + 1 >= 1)
        {
            beforeKRevCnt -= revCnt[i - k + 1];
        }
    }
    // 记录结果
    int currentAnsm = 0;
    for (int i = 1; i <= n; i++)
    {
        currentAnsm += revCnt[i];
        // 无解,从左到右,最右的不足k个不能动
        if (i + k - 1 > n && revCnt[i] != 0)
        {
            return;
        }
    }
    if (currentAnsm < ansm)
    {
        ansm = currentAnsm;
        ansk = k;
    }
}
// 从右到左
void solveLefter()
{
    int beforeKRevCnt = 0;
    // 从左到右边考虑,[i+1,i+k-1]反转的数量会影响到i的位置
    for (int i = n; i >= 1; i--)
    {
        bool isFront = front[i];
        if (beforeKRevCnt % 2 != 0)
        {
            isFront = !front[i];
        }
        if (!isFront)
        {
            revCnt[i] = 1;
        }
        beforeKRevCnt += revCnt[i];
        if (i + k - 1 <= n)
        {
            beforeKRevCnt -= revCnt[i + k - 1];
        }
    }
    // 记录结果
    int currentAnsm = 0;
    for (int i = 1; i <= n; i++)
    {
        currentAnsm += revCnt[i];
        // 无解,从右到左,最左的不足k个不能动
        if (i - k + 1 < 1 && revCnt[i] != 0)
        {
            return;
        }
    }
    if (currentAnsm < ansm)
    {
        ansm = currentAnsm;
        ansk = k;
    }
}
int main()
{
    input();
    ansk = 1, ansm = n;
    for (int i = 1; i <= n; i++)
    {
        k = i;
        flushRevCnt();
        solveRighter();
    }
    for (int i = 1; i <= n; i++)
    {
        k = i;
        flushRevCnt();
        solveLefter();
    }
    printf("%d %d\n", ansk, ansm);
    return 0;
}

你可能感兴趣的:(算法,数据结构)