反转(开关问题)(挑战程序设计竞赛)


例题1

题目描述

农场主约翰把他的**N(1 ≤ N ≤ 5,000)**头奶牛排成一排,很多都是面向前方的,就像好奶牛一样。然而,有些人是面向过去的,而他需要所有人都面向未来,以使他的生活变得完美。

幸运的是,FJ最近买了一台自动翻牛机。因为他购买的是折扣型,所以必须一次性转**K(1 ≤ K ≤ N)**头,且只能转排在一起的奶牛。每次使用该机器时,它都会反转该行中一个连续的K头奶牛组的朝向(不能在少于K头的奶牛上使用,例如在该行的任意一端)。每头奶牛都保持着和之前一样的位置,但最终却朝着相反的方向。一头牛一开始朝前,机器就会把它向后转,反之亦然。

因为FJ必须选择一个单一的,不变的值K,请帮助他确定K的最小值,使机器所需的操作数量最小化,以使所有的牛面朝前。还要确定M,即使用K值使所有奶牛面向前方所需的机器操作的最小数量。

输入描述

第1行:单个整数:N
第2 . .N+1行:第i+1行包含一个字符F或B,表示**cowi**是面向前还是面向后。

输出描述

第1行:两个用空格分隔的整数:K和M

输入

7
B
B
F
B
F
B
B

输出

3 3

提示

对于K = 3,机器必须操作三次:翻牛(1,2,3),(3,4,5),最后(5,6,7)

算法分析

1 2

代码

int N;
int dir[Max_N];//表示每头牛的朝向
int flag[Max_N];//flag[i]标志以第i头牛为首的k个位置的牛是否需要转向 1表示需要转向 0表示不需要转向

int calc(int k)
{
    fill(flag, flag + N, 0);//初始化标志数组
    int res = 0;
    int sum = 0;  //sum=flag[i-1]+flag[i-2]+...+flag[i-k+1]记录在没有计算到第i头牛时,第i头牛被连带转向了几次
    for (int i = 0; i + k <= N; i++)
    {
      	//类似尺取法
        if((dir[i] + sum) % 2 != 0)//表示第i头牛正面向后方,需要转向
        {
            res++;
            flag[i] = 1;
        }

        sum += flag[i];
        if(i - k + 1 >= 0)
        {
            sum -= flag[i - k + 1];
        }
    }
  	//类似尺取法
    
    //最后一个区间的情况已经被前K-1个区间确定了
    //枚举这K-1个区间的sum情况
    //即可判断是否全部转向正确
    for (int i = N - k + 1; i < N; i++)
    {
        if((dir[i] + sum) % 2 != 0)//表示第i头牛正面向后方,需要转向
        {
            return -1;
        }
        if(i - k + 1 >= 0)
        {
            sum -= flag[i - k + 1];
        }
    }

    return res;
}

//枚举每一种的K值
void solve()
{
		int k = 1, m = n;
    for (k = 1; k <= N; k++)
    {
        m = calc(k);
        if(m < M && m > 0)
        {
            M = m;
            K = k;
        }
    }
    cout << K << " " << M << endl;
}

例题2

题目描述

农夫约翰知道一头智力满足的奶牛是一头快乐的奶牛,它会产更多的奶。他为奶牛安排了一项脑力活动,让它们操作一个M × N的网格**(1 ≤ M ≤ 15;1 ≤ N ≤ 15)**方片,每方片一面为黑色,另一面为白色。

正如人们所猜测的那样,当一个白色的瓦片翻转时,它会变成黑色;当一个黑色的瓦片翻转时,它会变成白色。当奶牛翻转瓷砖使每一块瓷砖的白色面朝上时,它们就会得到奖励。然而,奶牛的蹄子相当大,当它们试图翻转某个瓦片时,它们也会翻转所有相邻的瓦片(与翻转的瓦片共享完整边缘的瓦片)。由于抛硬币很累人,奶牛们想尽量减少抛硬币的次数。

帮助奶牛确定最少需要翻几次,以及达到最少需要翻几次的位置。如果有多种方法可以用最少的翻转次数来完成任务,那么在将输出视为字符串时,请返回字典排序最少的方法。如果任务不可能完成,打印一行“IMPOSSIBLE”。

输入描述

第1行:两个用空格分隔的整数:M和N
第2 . .M+1行:第i+1行描述网格第i行的颜色(从左到右),用N个空格分隔的整数,其中1表示黑色,0表示白色

输出描述

第1 . .M行:每行包含N个以空格分隔的整数,每个整数指定翻转该特定位置的次数。

输入

4 4
1 0 0 1
0 1 1 0
0 1 1 0
1 0 0 1

输出

0 0 0 0
1 0 0 1
1 0 0 1
0 0 0 0

算法分析

3

解题代码

#include <iostream>
#include <stack>
#include <cstring>
using namespace std;

int m,n;
int mp[20][20]; //输入
int f[20][20];  //存储中间过程的按钮情况
int ansf[20][20];   //存储结果的按钮情况
int dx[5] = {-1, 0, 0, 0, 1};
int dy[5] = {0, -1, 0, 1, 0};

//查询一个点的颜色
int get(int x, int y)
{
    int c = mp[x][y];
    for(int d = 0; d < 5; d++){
        int x2 = x + dx[d], y2 = y + dy[d];
        if(x2 >= 0 && x2 <= m + 1 && y2 >= 0 && y2 <= n + 1)
            c += f[x2][y2];
    }
    
    return c % 2;
}
//查询一个点的颜色

//求出在第一行按钮确定的情况下的最小操作数
int calc()
{
    for(int i = 2; i <= m; i++)
        for(int j = 1; j <= n; j++){
        //如果这行上面的格子是黑的,那么就要在这行翻转,使得上行都为白色
            if(get(i - 1, j)){
                f[i][j] = 1;
            }
        }

    //检测最后一行是否存在黑色格子
    //如果存在黑色格子就说明这个情况不正确
    for(int i = 1; i <= n; i++){
        if(get(m, i))
            return -1;
    }
    
    //统计按下按钮的次数
    int res = 0;
    for(int i = 1; i <= m; i++)
        for(int j = 1; j <= n; j++)
            res += f[i][j];
    
    return res;
}
//求出在第一行按钮确定的情况下的最小操作数

void solve()
{
    int ans = -1;
    
    for(int i = 0; i < (1 << n); i++){
        memset(f, 0, sizeof(f));
        
        //十进制转换成二进制
        //第一行按钮的情况可以看成一个二进制代码
        for(int j = 1; j <= n; j++){
            //从右往左分配进制,为了保证最后答案是字典序最小
            //例如0000,0001,0010......
            f[1][n - j + 1] = i >> (j - 1) & 1;
        }
        //十进制转换成二进制
        
        int num = calc();
        if(num >= 0 && (ans < 0 || ans > num)){
            ans = num;
            memcpy(ansf, f, sizeof(f));
        }
    }
    
    if(ans < 0)
        cout << "IMPOSSIBLE\n";
    else{
        for(int i = 1; i <= m; i++){
            for(int j = 1; j <= n; j++){
                cout << ansf[i][j] << " ";
            }
            cout << endl;
        }
    }
    
}

int main()
{
    cin >> m >> n;
    
    for(int i = 1; i <= m; i++){
        for(int j = 1; j <= n; j++)
            cin >> mp[i][j];
    }
    
    solve();
    
    return 0;
}

例题3:

熄灯问题(OpenJ_Bailian - 2811)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

省下洗发水钱买书

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值