反转(开关问题) POJ 3276

POJ 3276

题意:n头牛站成线,有朝前有朝后的的,然后每次可以选择大小为k的区间里的牛全部转向,会有一个最小操作m次使得它们全部面朝前方。问:求最小操作m,再此基础上求k。

题解:1、5000头牛不是小数目,再怎么也得要n^2的算法,其中,枚举k是需要的,这就有n了,只能想办法给出一个n在O(n)时间内求出最小次数了。

   2、对于给定的k,要想O(n)内把次数算出来,即只能扫一遍,一想到的必定是从前往后扫,遇到面朝后的就转头,但这一转牵扯太多,要改太多东西,k一大直接崩溃。

   3、对于每次扫描到的第i个点,都至多只能改一次才能保证效率,即只改变化的。将牛的朝向弄成依赖型,即后者依赖于前者,这样在一个区间内[a,b]翻转时,实际上[a+1,b]的依赖关系是没有改变的,改变的只有a,b+1。

   4、综上,设置一种关系表示每头牛与前一头牛的朝向,最简单的就是同向与反向的差异,不妨令同向为0,反向为1,为了使得最后都朝前,可以令一头虚拟牛(即0号牛)头朝前,然后第一头牛依赖于它。

   5、因此,每次检查时,只需要更改a和a+k位置的牛的依赖关系便可以解决了,最后在检查一下剩余的牛是否全是0就结束了。

#include <cstdio>
#include <iostream>
#include <cmath>
#include <memory.h>
#include <cstring>



using namespace std;

int N;
char tmp, tmp1;
int arr[5002];
int arr1[5002];

int K,M;

int test(int k)
{
    memcpy(arr1,arr,sizeof(arr));
    int time = 0;
    for(int i=1; i<=N-k+1; i++)
    {
        if(arr1[i] == 1)
        {
            arr1[i] = 0;
            arr1[i+k] = abs(arr1[i+k]-1);
            time++;
        }
    }

    for(int i=N-k+1; i<=N; i++)
    {
        if(arr1[i] == 1)
            return 0;
    }
    return time;

}



void caculate()
{
    K = 1;
    M = N;
    int tmpM;
    for(int i=1; i<=N; i++)
    {
        tmpM = test(i);

        if(tmpM>0 && tmpM<M)
        {
            M = tmpM;
            K = i;
        }
    }

}



int main(int argc, char** argv)
{

    //freopen("E:/sample_input.txt", "r", stdin);

    while(scanf("%d",&N)!=EOF)
    {
        memset(arr, 0, sizeof(arr));
        tmp1 = 'F';
        for(int i=1; i<=N; i++)
        {

            scanf(" %c", &tmp);
            if(tmp == tmp1)
                arr[i] = 0;
            else
                arr[i] = 1;
            tmp1 = tmp;
        }

        caculate();

        cout << K << " " << M << endl;

    }
    return 0;
}
原文地址:https://www.cnblogs.com/scarecrow-blog/p/4572129.html