POJ 3276 Face The Right Way
思维很巧妙,因此记录一下自己的理解的思路。。
题目大意
N    ( 1 ≤ N ≤ 5000 ) N\,\,(1 \leq N \leq 5000) N(1≤N≤5000)头牛排成一列,每头牛的方向都是向前或者向后。为了让所有的牛都朝前,农夫约翰买了一台自动转向的机器。这个机器的作用是使连续的 K K K的头牛转向,请求出让所有牛都面向前面的最少操作机器次数 M M M,以及在这个操作次数下的 K K K值。
其中 B B B表示朝后, F F F表示朝前
样例输入
7
B
B
F
B
F
B
B
样例输出
3 3
思路
要做这道题,首先要明白对于给定的 K K K来讲,一定有以下两点
- 对于任意一个长度为 K K K的区间,至多转向 1 1 1次,因为多余的转向没有任何意义
- 如果一头牛已经是正向了,那么它不需要转向,也就是包含它的区间都不能反转
想清楚这两点,题目就相当于求需要反转方向的区间的个数。对于给定的 K K K(假设下标都是从** 1 1 1**开始),其中包含 n + 1 − k n+1-k n+1−k个区间,例如有 8 8 8头牛, K = 2 K=2 K=2。那么就有 7 7 7个区间,分别是
[ 1 , 2 ] 、 [ 2 , 3 ] 、 [ 3 , 4 ] 、 [ 4 , 5 ] 、 [ 5 , 6 ] 、 [ 6 , 7 ] 、 [ 7 , 8 ] [1,2]、[2,3]、[3,4]、[4,5]、[5,6]、[6,7]、[7,8] [1,2]、[2,3]、[3,4]、[4,5]、[5,6]、[6,7]、[7,8]
实际上因为 K K K不确定,那么需要做的就是枚举 K K K的可能范围 [ 1 , n ] [1,n] [1,n]。对于每一个 K K K,计算一个相应的 M M M,求出其中最小的 M M M即可。
直接枚举
思路很简单,正向的牛不去动它,反向的牛反转区间即可。但是这样做会超时,时间不允许
#include <iostream>
#include <cstdio>
#define N 5005
using namespace std;
bool dir[N], a[N];
int n;
int k, m, M = 0x3f3f3f3f, K;
// 1表示B,0表示F,最终要全部变为F
int main () {
int f;
char c;
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("\n%c", &c);
if(c == 'B')
dir[i] = 1;
}
for(k = 1; k <= n; k++) {
m = 0;
for(int i = 1; i <= n; i++) a[i] = dir[i];
int loc;
while(1) {
for(loc = 1; loc <= n && !a[loc]; loc++);//找到第一头反向的牛
if(loc > n) break;
for(int j = loc; j <= loc+k-1; j++)//将这个牛以及以这个牛为首的区间反转
a[j] = !a[j];
m ++;
}
if(m < M) { //更新答案
M = m;
K = k;
}
}
printf("%d %d\n", K, M);
return 0;
}
因此白书上提供的一种方法使用一个数组 f [ i ] f[i] f[i]来表示其中区间 [ i , i + k − 1 ] [i, i+k-1] [i,i+k−1]的状态,如果这个区间反转过,那么 f [ i ] = 1 f[i] = 1 f[i]=1否则 f [ i ] = 0 f[i] = 0 f[i]=0
那么对于一头牛 i i i来讲,我们从前往后考虑,能够影响到这头牛的区间是 [ i − k + 1 , i − 1 ] [i-k+1, i-1] [i−k+1,i−1]。所谓影响到这头牛的意思就是在这个 [ i − k + 1 , i − 1 ] [i-k+1, i-1] [i−k+1,i−1]区间的每一头牛作为区间首的话,进行反转,牛 i i i也会进行相应的反转。
因此如果 s u m = ∑ j = i − k + 1 i − 1 f [ j ] sum = {\sum_{j=i-k+1}^{i-1}}f[j] sum=∑j=i−k+1i−1f[j]为奇数,意味着能够影响第 i i i头牛的区间被反转了奇数次,那么第 i i i头牛一定和初始方向相反,如果是偶数次,那么一定和初始方向相同。
那么对于每一头牛,我们就可以在常数时间下来考虑。对于每一头牛,如果一开始是正向初始为 0 0 0,一开始是负向,初始为 1 1 1。很明显如果当前牛的状态加上 s u m sum sum是奇数,则 f [ i ] = 1 f[i] = 1 f[i]=1。
同时还有一点,因为 n n n不一定是 k k k的整数倍数,因此要分成两段来判断
代码
#include <iostream>
#include <cstdio>
#define N 5005
#define INF 0x3f3f3f3f
using namespace std;
bool dir[N];
int n;
int k, m, M = INF, K;
int calc (int k) {
int res = 0, sum = 0;
int f[N] = {0};
for(int i = 1; i + k <= n + 1; i++) { //枚举以每个牛作为起点能到达的最远的区间位置
if((dir[i] + sum) & 1) { //如果当前牛的状态以及它被影响的状态让他是反向的,则需要反转区间
res ++; //操作次数++
f[i] = 1;
}
sum += f[i]; //累计
if(i >= k) //因为每次只记录k-1个连续长度的区间,所以要不断减去前面不用的状态。
sum -= f[i-k+1];
}
for(int i = n-k+2; i <= n; i++) { //枚举后面的牛的情况
if((dir[i] + sum) & 1) //因为此时后面的牛的情况已经不能组成一个长度为k的区间,如果出现反向,则无法达到所有牛正向的状态
return INF;
if(i >= k)
sum -= f[i-k+1];
}
return res;
}
int main () {
int f;
char c;
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("\n%c", &c);
if(c == 'B')
dir[i] = 1;
}
for(k = 1; k <= n; k++) { //枚举所有的K
m = calc(k);
if(m < M) {
M = m;
K = k;
}
}
printf("%d %d\n", K, M);
return 0;
}