POJ 3276 Face The Right Way【开关问题】

题目链接http://poj.org/problem?id=3276

题意:有n头牛排成一列,面朝前(F)或者面朝后(B)。约翰想让所有的牛都面朝前,他每次可以反转连续的k头牛。求为了让所有牛都面朝前需要最少反转的次数M和对应的最小连续长度K值。

题目分析:我们首先可以想到n^3的暴力,即枚举反转长度K,n^2反转。 超时是一定的,但是这个想法可以给我们一些启发。

首先,枚举反转长度K这个操作是一定的,因为在不同的K的情况下,翻转次数是完全无关的,我们不可能在不枚举K的情况下得知K取什么值可以使翻转次数最少。

既然K必须枚举了,那么问题转化为给出确定的反转长度K,求反转次数。

n^2的暴力求翻转次数是超时的,所以我们要想出一个更快的方法。

因为反转的区间是一定连续的,并且,如果我们从左往右依次看,第i头牛是否需要反转只与他自己的朝向和他前面k-1头牛的反转情况有关,所以我们这是典型的开关问题。

用f[i]表示对区间[i,i+k-1]是否反转,f[i]=0表示不反转i及其后边的K-1头牛,f[i]=1表示反转。

那么对于每一头牛,他的决策只与自己的朝向a[i]与之前的可以影响他的其他牛的反转次数和sum有关。

#include<cstdio>
#include<iostream>
#include<cstring>
int a[5010],f[5010];//a牛的初始方向(0:F 1:B)需要翻转B 
int n;//f[i]表示是否对区间[i,i+k-1]进行了反转 
using namespace std;
int slove(int k)
{
	memset(f,0,sizeof(f));
	int sum=0,ans=0;
//先对从1到n-k+1的牛进行反转判断
	for (int i=1; i<=n-k+1; i++){
		if ((a[i]+sum)%2==1) {
			ans++;
			f[i]=1;
		
		}
		sum=sum+f[i];
		if (i-k+1>=1) sum=sum-f[i-k+1];
        //当i-k+1>=1说明f[i-k+1]的反转结束了,
        //对之后的牛将不再产生影响,所以要减去
	}

//对于最后的K-1头牛,他们是无法再反转的了(因为数量不足K)
//我们只需将之前对此的影响继承过来,并判断朝向即可。
	for (int i=n-k+2; i<=n; i++){
		if ((a[i]+sum)%2==1) return -1;
        //返回-1表示当前的K的情况下无解。
		if (i-k+1>=1) sum=sum-f[i-k+1];
	}
	return ans;
}
 
int main()
{
	char s[2];
	scanf("%d",&n); 
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s);
		if(s[0]=='F') a[i]=0; else a[i]=1;
	}
    //ans表示翻转次数,ansk表示反转长度
	int ans=100000,ansk=100000;
	for (int k=1; k<=n; k++){
		int c=slove(k);
		if ((c!=-1)&&(c<ans)){
			ans=c;
			ansk=k;
		}
	}
	cout<<ansk<<" "<<ans<<endl; 
    //注意题目要求的是先输出长度,
    //万恶的样例3 3让我迷惘了好久
	return 0;
} 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值