POJ 3276 Face The Right Way(开关,反转)详解(尺取+枚举两种做法)

来源:互联网 发布:遗传算法的优缺点 编辑:程序博客网 时间:2024/06/06 19:06



Face The Right Way
Time Limit: 2000MS Memory Limit: 65536KTotal Submissions: 4410 Accepted: 2045

Description

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

7BBFBFBB

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)

这题开关的思想:

首先,交换区间反转的顺序对结果是没有影响的。此外,可以知道对同一个区间进行两次以上的反转是多余的,由此,问题就转化成了求需要被反转的区间的集合。于是我们先考虑一下最左端的牛。包含这头牛的区间只有一个,因此如果这头牛面朝前方,我们就能知道这个区间不需要反转。

反之,如果这头牛面朝后方,对应的区间就必须进行反转了。而且在此之后这个最左的区间就再也不需要考虑了。这样一来,通过首先考虑最左端的牛,问题的规模就缩小了1.不断的重复下去,就可以无需搜索求出最少所需的反转次数了。



题意:
N个牛  每个都有一定的方向 B背对 F表示头对着你  给你一个装置   每次可以选择连续的K个牛反转方向  问你如何选择K  使得操作数最少    k也应尽量小
思路:这题也是开关问题,至不过多加了一步枚举K,这里反转的k可能有些大,直接枚举肯定超时,所以要优化反转的策略,不能一个个的转了。。这题网上主要有两种写法,一种是根据相邻点的相互关系,一种是区间内对某个点影响的和;

第一种的题解:引自:http://www.cnblogs.com/tmeteorj/archive/2012/10/11/2720537.html

题解:1、5000头牛不是小数目,再怎么也得要n^2的算法,其中,枚举k是需要的,这就有n了,只能想办法给出一个n在O(n)时间内求出最小次数了。

   2、对于给定的k,要想O(n)内把次数算出来,即只能扫一遍,一想到的必定是从前往后扫,遇到面朝后的就转头,但这一转牵扯太多,要改太多东西,k一大直接崩溃。

   3、对于每次扫描到的第i个点,都至多只能改一次才能保证效率,即只改变化的。将牛的朝向弄成依赖型,即后者依赖于前者,这样在一个区间内[a,b]翻转时,实际上[a+1,b]的依赖关系是没有改变的,改变的只有a,b+1。

   4、综上,设置一种关系表示每头牛与前一头牛的朝向,最简单的就是同向与反向的差异,不妨令同向为0,反向为1,为了使得最后都朝前,可以令一头虚拟牛(即0号牛)头朝前,然后第一头牛依赖于它。

   5、因此,每次检查时,只需要更改a和a+k位置的牛的依赖关系便可以解决了,最后在检查一下剩余的牛是否全是0就结束了。

具体解释一下:这里让第0头牛是F,这样第一头牛如果是1就是跟他反向,然后后面也是反向的就是0了,从左遍历遇到了1,反转到K,后面跟他一样的也转了,就是0,假设后面的牛跟第一头牛相反,就是它本身就是往前的,那么遍历遇到1,后面也是1,其实就是前面那个1把原来正确的又转反了。。。可以发现,是1的其实都是需要转的,这里要好好理解~改变k头牛的方向,只有这个区间第一头牛跟区间外的第一头牛的关系变了,所以i跟i+k都要取反,枚举到n-k+1,注意一定要到这里,而不是n-k,因为n-k+1到n有k个数字,这个区间也要转反的。。。一开始只枚举到k,觉得这样n这个点就更新了,但是n-k+1这个点没有更新啊。。。因为这个点也要转啊。。。


1,根据相互关系所做的代码:

#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>using namespace std;const int maxn = 5e3 + 5;const int INF = 1 << 30;int book[maxn], temp[maxn], n, ansnum, ansk;char pre, now;int main(){    while(~scanf("%d", &n))    {        pre = 'F';        for(int i = 1; i <= n; i++)  //这种方式可能想不到的。。。        {            scanf(" %c", &now);            if(pre == now)                book[i] = 0;            else                book[i] = 1;            pre = now;        }        ansnum = INF;        for(int k = 1; k <= n; k++)        {            memcpy(temp, book, sizeof(book)); //memcpy对int类型也可以用            int cnt = 0, flag = 0;            for(int i = 1; i <= n-k+1; i++)            {                if(temp[i])                    cnt++, temp[i+k] ^= 1;  //这样子就相当于反转了            }            for(int i = n-k+2; i <= n; i++)  //如果n-k+2里面,如果有朝后的就没有办法再有办法转动了。。。因为前面的保证了前面的都是往前的,这里没法转了            {                if(temp[i])                {                    flag = 1;                    break;                }            }            if(!flag)            {                if(ansnum > cnt)                {                    ansnum = cnt;                    ansk = k;                }            }        }        printf("%d %d\n", ansk, ansnum);    }    return 0;}

2,一般的做法,找区间内影响这个点的和,用尺取法做

题解:优化的方法是计算第i头牛是否要翻转的时候,只需要知道第i-k+1头到第i头之间的翻转次数,那么维护这个次数即可。每次向右移动一格,需要看看左边出去的那格(第i-k格)是翻转了没有,维护好即可。这样扫一遍的复杂度是O(n)。

<span style="font-family:SimHei;font-size:18px;">由于交换区间翻转顺序对结果没影响,所以从左往右对于需要 翻转的牛进行反转,同时记录对该区间其他牛的影响</span>
<span style="font-family:SimHei;font-size:18px;">即cal中的sum, 对于最后部分</span><span style="font-family: SimHei;font-size:18px; line-height: 24.05px;">无法翻转的区间检查是否有反向牛,若有则方案失败</span>
具体解释:比如i这头牛,转过几次受什么影响?肯定是i-k+1到i这k头转过的影响了,比如你i-k+1这头牛(区间的第一头牛)要转,后面的k头牛都要转,所以你i也要转,同理

中间的某头牛也是,他要转了,你i在他区间内更要转了,但是如果在i+1往后还有i-l往前,你爱怎么转怎么转,都影响不到我,因为区间就是k啊,所以只需要统计对每个i所在区间k对他有影响的次数就行,也就是需要转的次数,就是为a[j] = 1的个数,这里的记录方式就是用了尺取法了,(如果尺取法不会,我写过博客,可以先去学一下:http://blog.csdn.net/qq_34374664/article/details/52637132)用一个sum记录,少于k就不断sum+,超过了就把最左面的从sum里减去

#include <cstdio>#include <cstring>#include <algorithm>#include <iostream>using namespace std;const int maxn = 5e3 + 5;const int INF = maxn;int dir[maxn], sum, cnt, ansnum, ansk, n, book[maxn]; //book记录与对sum有影响的,dir记录牛的方向int solve(int k){    memset(book, 0, sizeof(book));    int sum = 0, cnt = 0;    for(int i = 1; i <= n - k + 1; i++)    {        if((sum + dir[i])%2)            cnt++, book[i] = 1;        sum += book[i];  //这是对后面有影响的,+上        if(i-k+1 >= 1) sum -= book[i-k+1]; //判断要不要剪去,是否超过k范围了    }    for(int i = n - k + 2; i <= n; i++)     {        if((dir[i]+sum)%2 == 1) return -1;  //这里要return -1,因为存在是0的答案啊。。。以后不是返回对错,都返回-1,因为可能0也是一个答案        if(i - k + 1 >= 1) sum -= book[i-k+1];    }    return cnt;}int main(){    while(scanf("%d", &n) != EOF)    {        char ch;        for(int i = 1; i <= n; i++)        {            scanf(" %c", &ch);            if(ch == 'F') dir[i] = 0;            else dir[i] = 1;        }        ansnum = INF;        for(int k = 1; k <= n; k++)        {            int check = solve(k);            if(check >= 0 && check < ansnum)            {                ansnum = check;                ansk = k;            }        }        printf("%d %d\n", ansk, ansnum);    }    return 0;}

3.程序设计上的代码,写的比较清晰,分工明确

#include <iostream>#include <algorithm>#include <cstring>using namespace std;const int M =5100;int dir[M];//  dir[i] 0:F 1;Bint n; int f[M]; // f[i] i~i+k-1的区间是否被翻转过  int calc(int k) //判断第i只cow的State 只要判断 j=i-k+1~i-1 f[j]的累加和 %2 为0则State和初始State相同  {memset(f,0,sizeof(f));int res=0;int sum=0;for(int i=0;i+k-1<=n-1;i++){if((dir[i]+sum)%2!=0){res++;f[i]=1;}sum+=f[i];if(i-k+1>=0) // 第i+1cow的State = j=i-k+1~i-1 f[j] + f[i] -f[i-k+1]     //j=i-k+1~i-1多出f[i-k+1] {sum-=f[i-k+1];}  }for(int i=n-k+1;i<n;i++)// Check之后的cow的State {if((dir[i]+sum)%2!=0) //无解 {return -1;}if(i-k+1>=0)sum-=f[i-k+1];}return res;}//最左端的cow区间翻转后 之后的区间时候翻转就固定下来了 void solve() //包含最左端的cow的区间只有一个  如果是 F 则f[i]开头的区间就不必要翻转 ,找到第一个为B的COW进行翻转 反转后继续找到"最左端为B的cow进行反转即可"  {int K=1,M=n;for(int k=1;k<=n;k++) //一次翻转k格 {int m=calc(k); //最小步数m if(m>0&&m<M){M=m;K=k; }}  cout<<K<<" "<<M; }int main(){cin>>n;for(int i=0;i<n;i++){char c;cin>>c;if(c=='B'){dir[i]=1;} else{dir[i]=0;}}solve();return 0;} 



2 0
原创粉丝点击