您的位置:首页 > 其它

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

2017-05-05 11:24 281 查看
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 的为题转化为规模为 n−1 的问题。利用此贪心算法,枚举前n−k+1 头牛, 最后看下第n−k+2 n 头牛的状态是否全都朝向前方,就可以判断当前K是否可行, 并给出解。这个算法在Problem A. Oversized Pancake Flipper中写过。

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

好在前面的O(n2) 的算法可以优化为一个 O(n) 的算法:

对于第i头牛,它的当前的状态与第 i−k+1 i−1 头牛的位置是否反转过有关。这里在第i−k+1 头牛处反转,意味着(i−k+1,...i) 这些牛的状态都要改变。对于第i 头牛,只需要关注在i−k+1 i−1 处反转的次数sum。

第i头牛的状态为a[i],那么第i头牛的当前状态就为(sum + a[i] ) % 2, 是奇数则表示面朝后方,需要反转,偶数则表示面朝前方,不需要反转。

这样只需要维护一个sum值 和 记录以哪些牛为起始位置反转过的数组f[]。

sumi+1=sumi+f[i]−f[i−k+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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  poj