其实是有这类题型的,不多也不少,也不能说有特定的一种算法,但是有其一类非常巧妙的技巧,今天好好研究了一下,用一道题来引入
Face The Right Way POJ - 3276
Farmer John has arranged his N (1 ≤ N ≤ 5,000) cows in a row and many of them are facing forward, like good cows. Some of them are facing backward, though, and he needs them all to face forward to make his life perfect.
Fortunately, FJ recently bought an automatic cow turning machine. Since he purchased the discount model, it must be irrevocably preset to turn K (1 ≤ K ≤ N) cows at once, and it can only turn cows that are all standing next to each other in line. Each time the machine is used, it reverses the facing direction of a contiguous group of K cows in the line (one cannot use it on fewer than K cows, e.g., at the either end of the line of cows). Each cow remains in the same location as before, but ends up facing the opposite direction. A cow that starts out facing forward will be turned backward by the machine and vice-versa.
Because FJ must pick a single, never-changing value of K, please help him determine the minimum value of K that minimizes the number of operations required by the machine to make all the cows face forward. Also determine M, the minimum number of machine operations required to get all the cows facing forward using that value of K.
Input
Line 1: A single integer: N
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
Sample Input
7
B
B
F
B
F
B
B
Sample Output
3 3
Hint
For K = 3, the machine must be operated three times: turn cows (1,2,3), (3,4,5), and finally (5,6,7)
看这道题之前,先看一道简化版
翻硬币 蓝桥杯
问题描述
小明正在玩一个“翻硬币”的游戏。
桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零)。
比如,可能情形是:**oo***oooo
如果同时翻转左边的两个硬币,则变为:oooo***oooo
现在小明的问题是:如果已知了初始状态和要达到的目标状态,每次只能同时翻转相邻的两个硬币,那么对特定的局面,最少要翻动多少次呢?
我们约定:把翻动相邻的两个硬币叫做一步操作,那么要求:
输入格式
两行等长的字符串,分别表示初始状态和要达到的目标状态。每行的长度<1000
输出格式
一个整数,表示最小操作步数。
样例输入1
o****o****
样例输出1
5
样例输入2
o**o***o**
o***o**o**
样例输出2
1
先看这道题,我们知道每次翻一个区间,其长度都是固定的,比如,[1,2],[5,6]等等,问题是怎样才是最小的,就是说,对于一个已知区间,如果要使总是,其翻转次数不能超过2次,也就是说要么翻一次,要么不翻(我们可以用1,0表示),注意我们这里说的对象是区间,不是某个硬币,对于某个硬币翻转了多少次是不一定的。
另一个,假设我已经知道所需要翻的区间,那么他们翻区间的顺序不会影响我们的结果。
现在从最左边开始考虑,假如第一个硬币的状态和目标状态相同,则[0,1],这个需不需要翻转?,答案是不需要,反证:假设需要,那么第一个硬币的状态就会和所对应的目标状态不一样,那么最终,我还是需要一步在把[0,1]翻转,因为前面已经说过了同一个区间不需要翻转两次,所以不需要,同理,如果第一个状态和目标状态不一样,则这个区间一定要翻转,以此,我们就只需要从做到右顺次去翻硬币,只要不一样的,我就翻转这个区间
代码O(n^2):
import java.util.Scanner;
public class Main
{
static int count=0;
static char f[],c[];
public static void main(String...args)
{
Scanner sc=new Scanner(System.in);
String s1=sc.nextLine();
c=s1.toCharArray();
f=sc.nextLine().toCharArray();
change(0);
System.out.println(count);
}
static void change(int p)//我们用递归逐个去考虑每个位置
{
if(p==c.length-1)到最终长度即终止
return;
if(c[p]==f[p])
change(p+1);如果状态一样就去考虑下一个位置
else
{
count++;//操作数加1
if(c[p+1]=='o')//操作的后果会使下一个位置的硬币变成相反方向
c[p+1]='*';
else
c[p+1]='o';
change(p+1);//然后再去考虑下一个
}
}
}
好,现在再思考第一题,我们可以枚举k,从1到n,但是这样的话时间复杂度会达到O(n^3),超时是毫无疑问的,其实问题的关键在于,假设我们在考虑,第i个物体要不要,其状态不确定的,因为前面相关的区间(包含i)的翻转会改变i的转台,计算当前状态需要o(n)时间,问题的关键在于,计算当前状态我们可以优化到o(1)时间
代码
import java.util.Arrays;
import java.util.Scanner;
public class Main
{
static int a[],f[],n;
public static void main(String[]args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
a=new int[n];
String s;
for(int i=0;i<n;i++)
{
s=sc.next();
if(s.equals("B"))
a[i]=1;
else
a[i]=0;
}
int k=0;
int ans=n;
for(int i=1;i<=n;i++)
{
int x=check(i);
if(x!=-1)
{
if(ans>x)
{
k=i;
ans=x;
}
}
}
System.out.println(k+" "+ans);
}
static int check(int k)//返回的结果是在k下翻转的次数,如果无法得到目标状态,返回-1
{
int times=0;//记录次数
int sum=0;//
f=new int[n];//f数组用来记录[i,i-k+1]是否需要翻转
if(a[0]==1)
{
f[0]=1;//[0,k-1]这个区间翻转了一次
times++;
}
int i;
for(i=1;i+k<=n;i++)//巧妙之处在于,我们判断i的状态,则只需要去找,f[i-k+1]到f[i-1]这些区间,因为这些区间包含i,假设这区间翻转次数的和为偶数,那么,i的状态和原始相同,反之则相反,然后我们用一个sum来动态的维护f[i-k+1]到f[i-1]的和(核心)
{
if(i-k>=0)//区间和要右移,一开始的时候,防止索引越界
sum-=f[i-k];
sum+=f[i-1];//加上上一个
if(sum%2==0)//偶数
{
if(a[i]==1)//则要翻转
{
f[i]=1;//同事计算f[i]的值
times++;
}
}
else
{
if(a[i]==0)//反之,原来是正的,现在是反的,那么在翻一次
{
f[i]=1;
times++;
}
}
}
sum+=f[i-1];//以上翻转完毕,验证余下的是否都已经达到合法状态
for(;i<n;i++)
{
if(i-k>=0)
sum-=f[i-k];//因为接下的区间都没有翻转,所以sum只会减
if(sum%2==0)
{
if(a[i]==1)
return -1;
}
else
if(a[i]==0)
return -1;
}
return times;
}
}
完成
peace&love