POJ_3276_Face The Right Way_区间反转问题

待我今夜破百,上床睡觉如何?


题意:

区间反转问题,或者说是开关等问题,一次可以反转k个单位,问最少操作多少次,对应的k是多少?



Input

Line 1: A single integer: N (1 ≤ N ≤ 5,000)
Lines 2..N+1: Line i+1 contains a single character, F or B, indicating whether cow i is facing forward or backward.

Output

Line 1: Two space-separated integers: K and M


区间反转问题有贪心策略,高效算法就是基于这个贪心策略的前缀和改进。

对一个确定的k来说,贪心策略是这样的:我们每次从左往右搜到第一个需要反转的,由于反转左边的必须要再反转回来,并且两次反转没意义,我们必须要反转从这个需要反转的点开始的一个长度为k的区间,然后再进行同样的过程,把区间往右推。

注意完成上述过程后还要判断没遍历到的最右边的k-1个点是否完成了反转。

这样可以直接求出一个可行的策略,不必花费指数级时间去搜索,但是反转区间的过程开销也不小,求一次反转最优方案需要O(N^2)时间,这个题目还需要遍历所有K,需要O(N^3),无法接受,但是我们可以利用前缀和来优化区间反转过程。

由于某个点的朝向只跟它一开始的方向和该点被反转次数有关(根据有效信息可以O(1)解决),我们就可以统计出一共有多少个区间反转涉及了这个点。

设bool变量f(i)表示i点开头的区间是否被反转了,第i个点被反转的次数就是sum(f(i-k+1),f(i-1)),这个值线性变化,那么我们自然可以用O(1)时间完成维护。sum(f((i+1)-k+1),f((i+1)-1))=sum(f(i-k+1),f(i-1))+f((i+1)-k+1)-f(i-1)


代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
using namespace std;
#define mxn 5010
#define inf 0x3f3f3f3f
int n;
bool front[mxn];
bool f[mxn];
int main(){
	while(scanf("%d",&n)!=EOF){
		for(int i=0;i<n;++i){
			char tem;
			cin>>tem;
			front[i]=tem=='F'?true:false;
		}
		int m=inf;
		int k;
		for(int i=1;i<=n;++i){
			int cnt=0;
			int cur=0;
			memset(f,0,sizeof(f));
			for(int j=0;j+i<=n;++j){
				bool tem=front[j];
				if(cnt%2)	tem=1-tem;
				if(!tem){
					f[j]=1;
					++cur;
				}
				cnt=cnt+f[j];
				if(j-i+1>=0)	cnt-=f[j-i+1];
			}
			bool ok=true;
			for(int l=n-i+1;l<n;++l){
				bool tem=front[l];
				if(cnt%2==1)	tem=1-tem;
				if(tem==false){
					ok=false;
					break;
				}
				if(l-i+1>=0)	cnt-=f[l-i+1];
			}
			if(!ok)	continue;
			if(m>cur){
				m=cur;
				k=i;
			}
		}
		printf("%d %d\n",k,m);
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值