Face The Right Way (POJ 3276)(开关灯模型)

POJ 3276 Face The Right Way

思维很巧妙,因此记录一下自己的理解的思路。。

题目大意

N    ( 1 ≤ N ≤ 5000 ) N\,\,(1 \leq N \leq 5000) N(1N5000)头牛排成一列,每头牛的方向都是向前或者向后。为了让所有的牛都朝前,农夫约翰买了一台自动转向的机器。这个机器的作用是使连续的 K K K的头牛转向,请求出让所有牛都面向前面的最少操作机器次数 M M M,以及在这个操作次数下的 K K K值。

其中 B B B表示朝后, F F F表示朝前

样例输入
7
B
B
F
B
F
B
B
样例输出
3 3

思路

要做这道题,首先要明白对于给定的 K K K来讲,一定有以下两点

  • 对于任意一个长度为 K K K区间,至多转向 1 1 1次,因为多余的转向没有任何意义
  • 如果一头牛已经是正向了,那么它不需要转向,也就是包含它的区间都不能反转

想清楚这两点,题目就相当于求需要反转方向的区间的个数。对于给定的 K K K(假设下标都是从** 1 1 1**开始),其中包含 n + 1 − k n+1-k n+1k个区间,例如有 8 8 8头牛, K = 2 K=2 K=2。那么就有 7 7 7个区间,分别是

[ 1 , 2 ] 、 [ 2 , 3 ] 、 [ 3 , 4 ] 、 [ 4 , 5 ] 、 [ 5 , 6 ] 、 [ 6 , 7 ] 、 [ 7 , 8 ] [1,2]、[2,3]、[3,4]、[4,5]、[5,6]、[6,7]、[7,8] [1,2][2,3][3,4][4,5][5,6][6,7][7,8]

实际上因为 K K K不确定,那么需要做的就是枚举 K K K的可能范围 [ 1 , n ] [1,n] [1,n]。对于每一个 K K K,计算一个相应的 M M M,求出其中最小的 M M M即可。

直接枚举

思路很简单,正向的牛不去动它,反向的牛反转区间即可。但是这样做会超时,时间不允许

#include <iostream>
#include <cstdio>
#define N 5005
using namespace std;

bool dir[N], a[N];
int n;
int k, m, M = 0x3f3f3f3f, K;
// 1表示B,0表示F,最终要全部变为F
int main () {
	int f;
	char c;
	scanf("%d", &n);
	for(int i = 1; i <= n; i++) {
		scanf("\n%c", &c);
		if(c == 'B') 
			dir[i] = 1;
	}
	for(k = 1; k <= n; k++) {
		m = 0;
		for(int i = 1; i <= n; i++) a[i] = dir[i];
		int loc;
		while(1) {
			for(loc = 1; loc <= n && !a[loc]; loc++);//找到第一头反向的牛
			if(loc > n) break;
			for(int j = loc; j <= loc+k-1; j++)//将这个牛以及以这个牛为首的区间反转
				a[j] = !a[j];
			m ++;
		}
		if(m < M) { //更新答案
			M = m;
			K = k;
		}
	}
	printf("%d %d\n", K, M);
	return 0;
}

因此白书上提供的一种方法使用一个数组 f [ i ] f[i] f[i]来表示其中区间 [ i , i + k − 1 ] [i, i+k-1] [i,i+k1]的状态,如果这个区间反转过,那么 f [ i ] = 1 f[i] = 1 f[i]=1否则 f [ i ] = 0 f[i] = 0 f[i]=0

那么对于一头牛 i i i来讲,我们从前往后考虑,能够影响到这头牛的区间是 [ i − k + 1 , i − 1 ] [i-k+1, i-1] [ik+1,i1]。所谓影响到这头牛的意思就是在这个 [ i − k + 1 , i − 1 ] [i-k+1, i-1] [ik+1,i1]区间的每一头牛作为区间首的话,进行反转,牛 i i i也会进行相应的反转。

因此如果 s u m = ∑ j = i − k + 1 i − 1 f [ j ] sum = {\sum_{j=i-k+1}^{i-1}}f[j] sum=j=ik+1i1f[j]为奇数,意味着能够影响第 i i i头牛的区间被反转了奇数次,那么第 i i i头牛一定和初始方向相反,如果是偶数次,那么一定和初始方向相同。

那么对于每一头牛,我们就可以在常数时间下来考虑。对于每一头牛,如果一开始是正向初始为 0 0 0,一开始是负向,初始为 1 1 1。很明显如果当前牛的状态加上 s u m sum sum是奇数,则 f [ i ] = 1 f[i] = 1 f[i]=1

同时还有一点,因为 n n n不一定是 k k k的整数倍数,因此要分成两段来判断

代码

#include <iostream>
#include <cstdio>
#define N 5005
#define INF 0x3f3f3f3f
using namespace std;

bool dir[N];
int n;
int k, m, M = INF, K;

int calc (int k) {
	int res = 0, sum = 0;
	int f[N] = {0};
	for(int i = 1; i + k <= n + 1; i++) { //枚举以每个牛作为起点能到达的最远的区间位置
		if((dir[i] + sum) & 1) { //如果当前牛的状态以及它被影响的状态让他是反向的,则需要反转区间
			res ++; //操作次数++
			f[i] = 1;
		}
		sum += f[i]; //累计
		if(i >= k) //因为每次只记录k-1个连续长度的区间,所以要不断减去前面不用的状态。
			sum -= f[i-k+1];
	}
	for(int i = n-k+2; i <= n; i++) { //枚举后面的牛的情况
		if((dir[i] + sum) & 1) //因为此时后面的牛的情况已经不能组成一个长度为k的区间,如果出现反向,则无法达到所有牛正向的状态
			return INF;
		if(i >= k)
			sum -= f[i-k+1];
	}
	return res;
}
int main () {
	int f;
	char c;
	scanf("%d", &n);
	for(int i = 1; i <= n; i++) {
		scanf("\n%c", &c);
		if(c == 'B') 
			dir[i] = 1;
	}
	for(k = 1; k <= n; k++) { //枚举所有的K
		m = calc(k);
		if(m < M) {
			M = m;
			K = k;
		}
	}
	printf("%d %d\n", K, M);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值