待我今夜破百,上床睡觉如何?
题意:
区间反转问题,或者说是开关等问题,一次可以反转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.
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;
}