題目大意:
一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;
}