反转(开关问题)
Face The Right Way POJ No.3276
N头牛排成了一排,每头牛朝向前或者向后。为了让所有的牛都面向前方,农夫约翰买了一台自动转向的机器。这个机器在购买时就必须设定一个数值K,机器每操作一次,恰好使K头连续的牛转向。请求出为了让所有的牛都面向前方需要的最少的操作次数M和对应的最小的K。
限制条件:1<=N<=5000
输入
N = 7
BBFBFBB (F:面向前方,B:面向后方)
输出
K = 3
M = 3
1 B B FB F B B
2 F FB B F B B
3 F FF F B B B
4 F F F FF F F
分析:如果对每一头牛进行状态分析,则状态数有2^n个,无法在有限时间内找出答案。
1-区间反转的顺序对结果是没有影响的。
2-对同一区间进行两次反转是没有意义的。
-----> 问题变成了:求需要被反转的区间的集合
首先从最左端开始观察,包含这头牛的区间只有一个(连续区间长度是确定的)
若最左端的牛面向前方,得:这个区间不需要反转。
反之,若最左端的牛面向后方,则这个区间必须反转。 然后这个区间以后就不需要讨论了 (对同一区间进行两次反转是没有意义的)
因为排除了对同一区间反复操作这种无意义的操作之后,只要存在让所有的牛都面向前方的方法,那么操作(区间反转的顺序对结果是没有影响的)可以唯一确定。
但如果我们要把所有的K都求一遍,对于每个K我们都要从最左端来考虑N头牛的情况。在最坏的情况下需要进行N-K+1次的反转操作,而每次操作又要反转K头牛。所以复杂度就是O(N^3)不能再有限时间内解决。
所以要对其进行优化。
优化:对区间反转的部分进行优化。
设F(i)代表区间[i,i+K-1]是否进行了反转,若反转则为1,否则为0。
这样在考虑第i头牛的时候,若f[j]为奇数的话,那么这头牛的方向与起始方向是相反的,否则方向不变。
因为
所以每次都可以用常数时间计算出来,复杂度降低为O(N^2)
可以计算。
import java.util.Scanner;
public class FaceTheRightWay {
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
System.out.print("N=");
int N = cin.nextInt();
cin.nextLine();
String cow = new String();
cow = cin.nextLine(); // 输入牛的方向
int[] StateOfCow = new int[N];
for (int i = 0; i < N; i++) {
if (cow.charAt(i) == 'F')
StateOfCow[i] = 0;
else if (cow.charAt(i) == 'B')
StateOfCow[i] = 1;
}
// solve
int K = 1, M = N;
for (int k = 1; k <= N; k++) {
int m;
m = cal(k, N, StateOfCow);
if (m >= 0 && M > m) {
M = m;
K = k;
}
}
System.out.println("K=" + K);
System.out.println("M=" + M);
}
static int cal(int k, int N, int[] StateOfCow) {
// 区间[i,i+K-1]是否反转
int[] f = new int[N];
for (int n = 0; n < f.length; n++)
f[n] = 0;
int res = 0;
int sum = 0; // f的和
for (int i = 0; i + k <= N; i++) { // 计算区间[i,i+k-1]
if ((StateOfCow[i] + sum) % 2 != 0) {
// 区间最左端牛面朝后方
res++;
f[i] = 1;
}
sum += f[i];
if (i - k + 1 >= 0)
sum -= f[i - k + 1]; // 区间最左端的状态
}
for (int i = N - k + 1; i < N; i++) {
if ((StateOfCow[i] + sum) % 2 != 0)
return -1; // 无解
if (i - k + 1 >= 0)
sum -= f[i - k + 1];
}
return res;
}
}