题目大意:农夫有一群牛,牛排成了一排,现在需要把这些牛都面向正确的方向,农夫买了一个机器,一次可以处理k只牛,现在问你怎么处理这些牛才可以使操作数最小?
这道题很有意思,其实这道题是著名的开关问题的变种,我们可以用模拟去做,但是总不能一个一个地去翻转,不然就是2^n的复杂度了,我们要想另外一些方法。
我们知道,如果连续翻转同一片区域其实等于没有反转,再者,在翻转位置一样的情况下,先翻转谁其实没有太打大的关系,所以我们可以规定一个顺序(比如从左到右翻转),如果规定了一个方向翻转以后,我们可以翻转过后的左区域以后都不会受到影响,所以这样我们就可以像矩阵乘法一样把k不断增长来把复杂度降下去O(N^3)(K个长度,每个长度最多操作N个数,每个数翻转K次)但是这样对于N=5000来说还是太大了,我们要继续把复杂度降下去。
我们利用以前那种偏序集的方法,我们规定一个东西flip[i]:=i~i+k-1的是否需要翻转,如果需要翻转则是1,否则就是0,同时定义背面为1,正面是0
可以看到
∑((i+1)-k+1,i)flip[j]=∑(i-1,i-k+1)flip[j]+flip[i]-flip[i-k+1]
那么我们就可以不断减去两端的方法来求得flip的值了,这是个常数时间,所以复杂度降为O(N^2)
#include <iostream>
#include <algorithm>
#include <functional> using namespace std; static int dir[], if_flip[]; int solve(const int, const int); int main(void)//开关问题
{
int cows_sum, ans_k, ans_step, step;
char tmp;
while (~scanf("%d", &cows_sum))
{
getchar();
for (int i = ; i < cows_sum; i++)
{
scanf("%c", &tmp);
dir[i] = tmp == 'B' ? : ;//0表示前面,1表示后面
getchar();
}
ans_step = cows_sum; ans_k = ;
for (int k = ; k <= cows_sum; k++)
{
step = solve(cows_sum, k);
if (step >= && step < ans_step)
{
ans_step = step;
ans_k = k;
}
}
printf("%d %d\n", ans_k, ans_step);
}
return ;
} int solve(const int cows_sum,const int k)
{
//flip[i]:=i~i+k-1是否进行了翻转,翻转了就是1,否则就是0
int sum = , i, ans_step = ; memset(if_flip, , sizeof(if_flip));
for (i = ; i <= cows_sum - k; i++)
{
if ((dir[i] + sum) % == )//表明经过一系列翻转(如存在)还是背面朝上,则继续翻转
{
ans_step++;
if_flip[i] = ;
}
sum += if_flip[i];
if (i - k + >= )//注意这里是i-k+1!
sum -= if_flip[i - k + ];
}
for (; i < cows_sum; i++)//检查,剩下的段是不能翻转了(小于k了,所以只用检查一下符不符合规则就好)
{
if ((dir[i] + sum) % == )//说明还是有朝上的,说明这样的k是不行的
{
ans_step = -;
break;
}
else if (i - k + >= )//注意这里是i-k+1!
sum -= if_flip[i - k + ];
}
return ans_step;
}