POJ 3276 Face The Right Way [反转 (贪心)] 《挑战程序设计竞赛》 3.2

POJ 3276 Face The Right Way

题目大意:

一N头站成一排,有的面向前站,也有的面向后站。你有一种可以且只能反转连续的K头牛的朝向的机器。少于K头牛无法使用此机器。你需要让所有的牛都面朝前站。求一个最小的K,使得反转的次数M最少。

输入:

第一行, N
接下来2~N+1 行, 每行一个字符 ‘B’ 或者 ‘F’ 表示这头牛面朝前还是朝后。

输出:

K M

题解:

对于一个固定的k,求翻转所需要的最少次数,最朴素的有一个 O(n2) 的算法:
首先要明确,反转的次序是不重要的,对同一个区间反转两次和不反转的效果是一样的。
我们数组a[] 存所有牛的状态, 0表示面朝前方,1表示面朝后方。
对于第1头牛,只有 (1,2,...,k) 这一个反转可以影响它的状态, 所以如果第一头牛是’B’,则必须执行翻转 (1,2,...,k) ,改变 (1,2,...,k) 这些牛的状态。这时候,第2头牛的状态只与反转 (2,3,...,k+1) 有关。这就把问题规模为 n 的为题转化为规模为 n1 的问题。利用此贪心算法,枚举前 nk+1 头牛, 最后看下第 nk+2 n 头牛的状态是否全都朝向前方,就可以判断当前K是否可行, 并给出解。这个算法在Problem A. Oversized Pancake Flipper中写过。

需要求最小的K, 只需要把K 从1 到 N枚举一遍。这样算法的总体复杂度为 O(n3) 。应该是不能接受的。

好在前面的 O(n2) 的算法可以优化为一个 O(n) 的算法:
对于第 i 头牛,它的当前的状态与第 ik+1 i1 头牛的位置是否反转过有关。这里在第 ik+1 头牛处反转,意味着 (ik+1,...i) 这些牛的状态都要改变。对于第 i 头牛,只需要关注在ik+1 i1 处反转的次数sum。
第i头牛的状态为a[i],那么第i头牛的当前状态就为(sum + a[i] ) % 2, 是奇数则表示面朝后方,需要反转,偶数则表示面朝前方,不需要反转。
这样只需要维护一个sum值 和 记录以哪些牛为起始位置反转过的数组f[]。
sumi+1=sumi+f[i]f[ik+1]

代码

#include <iostream>
#include <cstring>
#define MAXN 5010
#define INF 0x3f3f3f3f
using namespace std;

int n;
int a[MAXN], f[MAXN];

int solve(int k) {
    memset(f, 0, sizeof(f));
    int ans = 0, sum = 0;
    for (int i = 0; i < n-k+1; i++) {
        if ((a[i] + sum) % 2 != 0) {
            ans++;
            f[i] = 1;
        }
        sum += f[i];
        if (i+1-k >= 0) {
            sum -= f[i+1-k];
        }
    }
    //判断当前k是否可行
    bool flag = false;
    for (int i = n-k+1; i < n; i++) {
        if ((a[i] + sum) % 2 != 0) {
            flag = true;
            break;
        }
        if (i+1-k >= 0) {
            sum -= f[i+1-k];
        }
    }
    return flag ? INF : ans;
}

int main() {
    ios::sync_with_stdio(false);
    cin >> n;
    char c;
    for (int i = 0; i < n; i++) {
        cin >> c;
        a[i] = (c == 'B' ? 1 : 0);
    }

    int K = 0;
    int M = INF;
    //枚举n次
    for (int i = 1; i <= n; i++) {
        int tmp = solve(i);
        if (tmp < M) {
            M = tmp;
            K = i;
        }
    }
    cout << K << " " << M << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值