题目链接:
http://poj.org/problem?id=3276
解题思路:
题目大意:
n头牛拍成了一列。每头牛或者向前或者向后。为了让所有的牛都面向前方,农夫约翰买了一台自动转向的机器。这个机器在购买时
就必须设定一个数值K,机器每操作一次恰好使K头连续的牛转向。请求出为了让所有的牛都能面向前方需要的最小的操作次数M和
对应最小的K。
算法思想:
首先我们来看看对于一个特定的K如何求出让所有的牛面朝前方的最小操作次数。如果把牛的方向作为状态进行搜索的话,由于状态
数有2的n次方个,是无法在时限内找出答案的。那么不搜索的话要怎么办呢?
首先,交换区间反转的顺序对结果时没有影响的。此外知道对同一区间进行两次以上的反转是多余的。由此,问题转化成了求需要
被反转的区间的集合。于是我们先考虑一下最左端的牛,包含这头牛的区间只有一个,因此如果这头牛面朝前方,我们就能知道这
个区间不需要反转,反之,如果这头牛面朝后方,对应的区间就必须进行反转了。而且再此之后这个最左的区间就再也不需要考虑
了。这样一来,通过首先考虑最左端的牛,问题的规模就缩小了1.不断地重复下去,就可以无需搜索求出最小所需的反转次数了。
此外,通过上面的分析可以知道,忽略掉对同一区间重复反转这类多余操作之后,只要存在让所有牛都朝前的方法,那么操作就和
顺序无关可以唯一确定了。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int n;
int dir[5005];//牛的方向(0:F, 1:B)
int f[5005];//区间[i,i+k-1]是否进行反转
//固定k,求对应的最小操作回数
//无解的话则返回-1
int cal(int k){
memset(f,0,sizeof(f));
int res = 0;
int sum = 0;//f的和
for(int i = 0; i+k <= n; i++){
//计算区间[i,i+k-1]
if((dir[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((dir[i]+sum)%2 != 0){
//无解
return -1;
}
if(i-k+1 >= 0){
sum -= f[i-k+1];
}
}
return res;
}
void solve(){
int K = 1,M = n;
for(int k = 1; k <= n; k++){
int m = cal(k);
if(m >= 0 && M > m){
M = m;
K = k;
}
}
printf("%d %d\n",K,M);
}
int main(){
while(~scanf("%d",&n)){
char ch;
for(int i = 0; i < n; i++){
getchar();
scanf("%c",&ch);
if(ch == 'F')
dir[i] = 0;
else
dir[i] = 1;
}
solve();
}
return 0;
}