反转问题
题意:
N头牛排成一列,每头牛或者朝前,或者朝后。为了让所有牛都面向前方,农夫约翰买了一台自动转向的机器。这个机器在购买时就必须设定一个数值K,机器每操作一次恰好使K头连续的扭转向。请求出为了让所有的牛都能面向前方所需要的最少操作次数M和对应的最小K。
限制条件:
1<=N<=5000
思路:
首先,可以确定翻转顺序对结果是没有影响的。其次,可以知道对同一个区间进行两次以上的反转是多余的。因此,我们可以从最左边开始,如果最左边那个需要反转就反转K头连续的牛,在判断第二个是否需要反转…..,这样写的话,复杂度有点高,最差o(n^3)不能解决这个问题。区间反转部分还算很容易优化的。
f[i]:=区间[i,u+K-1] 进行了反转的话就为1,否则为0
这样在考虑第i头牛时,如果
i−1
∑ f [ j] 为奇数的话,则着头牛的方向玉开始方向是相反的(就是看他之前被反转了多少次)。否则方向不
j=i−K+1
变。
由于:
∑(j=(i+1)-K+1,i) f[j] = ∑(j=i-K+1,i-1)f[j] + f[i] - f[i-K+1]
所以这个和每一次都可以在常熟时间内计算出来,复杂度就降为O(N^2)
AC代码:
//
// Created by luozujian on 17-12-2.
//
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
const int maxn = 5000+5;
int dir[maxn]; //牛的方向(0:F,1:B)
int f[maxn]; //区间[i,i-k+1]是否进行反转
int n;
//固定k 求最小的操作数 无解返回-1
int calc(int k)
{
memset(f,0,sizeof(f));
int res = 0;
int sum = 0;
for(int i=0;i+k<=n;i++)
{
if((sum+dir[i])%2 !=0)
{
//牛面朝后方。
f[i] = 1;
res++;
}
sum+=f[i];
if(i-k+1 >= 0)
{
sum-=f[i-k+1];
}
}
//检查剩下的牛是否有面朝后方的情况
for(int i=n-k+1;i<n;i++)
{
if((sum+dir[i])%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 = calc(k);
if(m>0 && M>m)
{
K = k;
M = m;
}
}
printf("%d %d\n",K,M);
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
char str[2];
scanf("%s",str);
printf("%c\n",str[0]);
if(str[0] == 'B') dir[i] = 1;
else dir[i] = 0;
}
solve();
return 0;
}