I am a teacher!

导航

C语言程序设计100例之(39):派对灯

例39   派对灯

题目描述

在 IOI98 的节日宴会上,我们有 n 盏彩色灯,它们分别从 1∼n 被标上号码。这些灯都连接到四个按钮:

按钮 1:当按下此按钮,将改变所有的灯:本来亮着的灯就熄灭,本来是关着的灯被点亮。

按钮 2:当按下此按钮,将改变所有奇数号的灯。

按钮 3:当按下此按钮,将改变所有偶数号的灯。

按钮 4:当按下此按钮,将改变所有序号是3k+1 (k∈[0,+∞)∩Z) 的灯。例如:1,4,7,10…

一个计数器 c记录按钮被按下的次数。当宴会开始,所有的灯都亮着,此时计数器 c 为 0。

你将得到计数器 c 上的数值和经过若干操作后某些灯的状态。写一个程序去找出所有灯最后可能的与所给出信息相符的状态,并且没有重复。

输入格式

第一行一个正整数 n(10≤n≤100);第二行一个整数 c(0≤c≤104),表示最后计数器的数值。

第三行若干个整数,表示最后亮着的灯,以 -1 结束。

保证不会有灯会在输入中出现两次。

输出格式

每一行是所有灯可能的最后状态(没有重复)。

每一行有 n 个字符,第 i 个字符表示 i 号灯。0 表示关闭,1 表示亮着。这些行必须从小到大排列(看作是二进制数)。

如果没有可能的状态,则输出一行 IMPOSSIBLE。

输入样例

10

1

-1

7 -1

输出样例

0000000000

0101010101

0110110110

        (1)编程思路。

        先考虑灯的状况。因为按下按钮1时,每1个灯改变1个灯的状态;按下按钮2时,每2个灯改变1个灯的状态;按下按钮3时,每2个灯改变1个灯的状态;按下按钮4时,每3个灯改变1个灯的状态。最小公倍数为6,因此,灯长度一定是6个灯一个循环节。也就是说,n盏灯按前6盏灯的状态进行重复。只需考虑前6盏灯的状态即可。

        再考虑按钮的情况,有如下事实:

        1)任意一个按钮按下2次,等于该按钮按下0次。由此可得:任意偶数次按按钮均可以转化为0或2或4次按不同按钮,任意奇数次按按钮均可以转化为1或3次按不同按钮。

        2)1、2、3这个3个按钮任意两个按下等同于第三个按下。

        由此,考虑6盏灯的所有状态与按过不同按钮的情况,得到如下组合情况

         char lamp[][7]= { {"111111"},      // 按过按钮:()、(1,2,3)    按的次数:0、3

                                   {"011011"},      // 按过按钮:(1,2,3,4)、(4)  按的次数:1、4

                                   {"101010"},      // 按过按钮:(1,2),(3)      按的次数:1、2

                                   {"001110"},      // 按过按钮:(1,2,4),(3,4)   按的次数:2、3

                                   {"010101"},      // 按过按钮:(1,3),(2)      按的次数:1、2

                                   {"110001"},      // 按过按钮:(1,3,4),(2,4)   按的次数:2、3

                                   {"000000"},      // 按过按钮:(1),(2,3)      按的次数:1、2

                                   {"100100"}      // 按过按钮:(1,4),(2,3,4)    按的次数:2、3

                };

        由上可知,若按按钮的次数大于或等于3次,6盏灯能组合出现的8种状态都会出现。

         定义数组 int num[8][2]={{0,3},{1,4},{1,2},{2,3},{1,2},{2,3},{1,2},{2,3}};保存6盏灯每种状态组合所对应的按不同按钮的次数。然后对按钮次数c<3时的各种灯状态对应的按钮次数组合进行穷举,验证各种按钮次数组合是否符合灯的状态组合,将符合要求的情况按序输出即可。

        (2)源程序。

#include <stdio.h>

#include <string.h>

char lamp[8][7]={{"111111"},{"011011"},{"101010"},{"001110"},

              {"010101"},{"110001"},{"000000"},{"100100"}};

int num[8][2]={{0,3},{1,4},{1,2},{2,3},{1,2},{2,3},{1,2},{2,3}};

// 每种灯的状态所对应的按不同按钮的次数

int lon[6]={0},loff[6]={0};

int judge(int index)

{

    int i;

    for (i=0;i<7;i++)

        if ((lon[i] && lamp[index][i]=='0')||(loff[i] && lamp[index][i]=='1'))

            return 0;

    return 1;

}

int main(void)

{

    int n,c;

    scanf("%d%d",&n,&c);

    int x;

    while (scanf("%d",&x) && x!=-1)

        lon[(x-1)%6]=1;

    while (scanf("%d",&x) && x!=-1)

        loff[(x-1)%6]=1;

    int cnt=0;

    char ans[8][7];

    int i,j;

    if (c<3)

    {

        for (i=0;i<8;++i)

          for (j=0;j<=1;++j)

          {

             if ((num[i][j]==c||num[i][j]==c-2)&&judge(i))

//num[i][j]==c-2成立时,只有有效按按钮次数为0

                    strcpy(ans[cnt++],lamp[i]);

          }

    }

    else

    {

        for (i=0;i<8;++i)

            if (judge(i))

                strcpy(ans[cnt++],lamp[i]);

    }

    if (cnt==0)

        printf("IMPOSSIBLE\n");

    else

    {

        for (i=0;i<cnt-1;i++)

          for (j=0;j<cnt-1-i;j++)

             if (strcmp(ans[j],ans[j+1])>0)

              {

                 char temp[7];

                 strcpy(temp,ans[j]);

                 strcpy(ans[j],ans[j+1]);

                 strcpy(ans[j+1],temp);

              }

        for(i=0;i<cnt;i++)

        {

            for (j=0;j<n;j++)

               printf("%c",ans[i][j%6]);

            printf("\n");

        }

    }

    return 0;

}

习题39

39-1  Easy Finding

        本题选自POJ题库 (http://poj.org/problem?id=3740)

Description

Given a M×N matrix A.  Aij ∈ {0, 1} (0 ≤ i < M, 0 ≤ j < N), could you find some rows that let every cloumn contains and only contains one 1.

Input

There are multiple cases ended by EOF. Test case up to 500.The first line of input is M, N (M ≤ 16, N ≤ 300). The next M lines every line contains N integers separated by space.

Output

For each test case, if you could find it output "Yes, I found it", otherwise output "It is impossible" per line.

Sample Input

3 3

0 1 0

0 0 1

1 0 0

4 4

0 0 0 1

1 0 0 0

1 1 0 1

0 1 0 0

Sample Output

Yes, I found it

It is impossible

        (1)编程思路。

        由于行数M最多为16行,因此可以用一个int型整数(32位>16)的每个二进制位来表示每列状态,例如,样例2中有4行,第1列中第2行和第3行为1,可用整数6(对应二进制数为0110)来表示,第2列中第3行和第4行为1,可用整数12(对应二进制数为1100)来表示。

        定义int col[301];,其中col[i]的值表示第i列的状态,在输入时,计算出col[i]的值。然后采用二进制枚举各行选中或不选中的状态,再用位运算来判断选的那些行是否满足每列只有一个1。

        令t = col[j] & i(i表示枚举的行的二进制状态),这样就把枚举的行里面的1取了出来。若t=0,说明枚举的这些行对应的某列里面一个1都不含,那么就不满足条件;若t!=0,再判断这列里面是不是只含一个1,若t &(t-1)等于0就是一个1,否则不是。

       (2)源程序。

#include <stdio.h>

#include <string.h>

int main()

{

    int m,n;

    while (scanf("%d%d", &m, &n)!=EOF){

        int col[301];

        int i,j,x,flag = 0;

        memset(col, 0, sizeof(col));

        for (i=0; i<m; i ++){

            for (j=0; j<n; j++){

                scanf("%d",&x);

                if (x==1) col[j] += (1 << i);

                if (i==m-1 && col[j]==0) flag = 1;

            }

        }

        if (flag){

            printf("It is impossible\n");

            continue;

        }

        for (i=1; i<(1<<m); i++){

            for (j = 0; j < n; j ++){

                int t = col[j] & i;

                if (t==0 || (t & (t-1))) {

                    break;

                }

            }

            if (j==n) {flag = 1; break; }

        }

        if (flag == 0)

           printf("It is impossible\n");

        else

           printf("Yes, I found it\n");

    }

    return 0;

}

39-2  翻格子

题目描述

给定一个n*m(1<=n,m<=16)的矩阵,矩阵中的每一个格子都有黑白两面,当翻动一个格子时,它的上下左右四个格子也要跟着翻转,问最后使格子全变为白色的最少翻转步数。

输入

输入包含多组测试用例。每个测试用例用两个整数R和C(1≤R、C≤16) 指定矩阵的大小。然后是R行,每行包含C个字符。字符可以是大写字母“X”(黑色)或点“.”(白色)。

输入R、C均为0时,表示输入结束。

输出

对于每个测试用例,输出一行,其中包含“You have to tap T tiles.”的句子,其中T是使所有格子变成白色所需的最小翻动数。如果无法翻动成功,则输出字符串“Damaged billboard.”。

输入样例

5 5

XX.XX

X.X.X

.XXX.

X.X.X

XX.XX

5 5

.XX.X

.....

..XXX

..X.X

..X..

1 5

...XX

5 5

...X.

...XX

.XX..

..X..

.....

8 9

..XXXXX..

.X.....X.

X..X.X..X

X.......X

X.X...X.X

X..XXX..X

.X.....X.

..XXXXX..

0 0

输出样例

You have to tap 5 tiles.

Damaged billboard.

You have to tap 1 tiles.

You have to tap 2 tiles.

You have to tap 25 tiles.

      (1)编程思路。

        由于翻动一个格子时,它的上下左右四个格子也要跟着翻转。这样对于第i行(i>=2)第j列格子的翻转情况受到其上一行(第i-1行)的制约,当且仅当第i-1行第j列的格子是“X”的时候,第i行第j列的格子才可翻转,使得第i-1行第j列的格子变为“.”,否则第i行第j列的格子不能翻转。这样依次逐行进行下去,当最后一行全变为白色,说明翻转成功。

        因此,本题只需枚举第一行的状态即可。状态数共1<<C(2C)个。

        一个非常重要的优化:当行数R小于列数C时,将矩阵转置,这样状态数由2C变为 2R

      (2)源程序。

#include <stdio.h>

#include <string.h>

#define INF 999999999

int main()

{

    int r,c;

    while (scanf("%d %d",&r,&c) && (r||c))

    {

        char map[17];

        int data[17];    // 用于将每一行的状态用一个二进制数来保存

        memset(data,0,sizeof(data)) ;

        int i,j,k;

        if (r>=c)

        {

            for (i=0 ; i<r ; i++)

            {

                data[i]=0;

                scanf("%s",map) ;

                for (j=0; j < c ; j++)

                {

                    if (map[j] == 'X')

                        data[i] |= 1 << j ;  // 这一行中是X的地方置为1

                }

            }

        }

        else     // 优化,当列比较少的时候就翻转过来用列

        {

            for (i = 0 ; i < r ; i++)

            {

                data[i] = 0 ;

                scanf("%s",map) ;

                for (j = 0 ; j < c ; j++)

                   if (map[j] == 'X')

                        data[j] |= 1 << i ;

            }

            int t;

            t=r;  r=c;  c=t;

        }

        int min=INF ;

        int a[17],tmp[17] ;

        for (i=0; i<(1<<c); i++)                               // 枚举状态,共有2的c次方个

        {

            for (j=0; j<r ; j++)

                a[j] = data[j];

            for (j=0; j<r ; j++)

            {

                tmp[j]= (j==0 ? i : a[j-1]);

                a[j] ^= tmp[j];                                   // 本行翻转

                a[j] ^= tmp[j] >> 1 ;                         // 右边翻转

                a[j] ^= tmp[j] << 1 & ((1 << c)-1) ;  // 左边翻转

                a[j+1]^= tmp[j] ;                             // 下一行翻转

            }

            if (!a[r-1])                                         // 判断最后一行是不是全白

            {

                int cnt = 0 ;

                for (j=0 ; j<r ; j++)

                {

                    for (k=tmp[j] ; k>0 ; k>>= 1)

                       if (1&k) cnt++ ;

                }

                if (cnt<min) min=cnt;

            }

        }

        if (min==INF)

            printf("Damaged billboard.\n") ;

        else

            printf("You have to tap %d tiles.\n",min) ;

    }

    return 0;

}

39-3  疾病管理

        本题选自POJ题库 (http://poj.org/problem?id=2436)

问题描述

D(1<=D<=15)种可感染奶牛的疾病(编号为1…D)正在农场蔓延。农夫约翰想要尽可能多地让他的N(1<=N<=1000)头奶牛挤奶。如果奶牛感染了超过K(1<=K<=D)种不同疾病,那么牛奶将被污染,必须全部丢弃,这头奶牛就无法挤奶,因为挤的奶也要丢弃。请帮助约翰确定可以正常挤奶的奶牛的最大数量。

输入

第1行:三个空格分隔的整数:N、D和K

第2…N+1行:第i+1行用一个或多个空格分隔的整数列表描述了奶牛i的疾病。第一个整数di是奶牛i的疾病数;其后的整数列举了实际的疾病。当然,如果di为0,则表示奶牛i没有感染任何疾病。

输出

1行:M,可以挤奶的奶牛的最大数量。

输入样例

6 3 2

0

1 1

1 2

1 3

2 2 1

2 2 1

输出样例

5

        (1)编程思路。

         因为疾病种类D不大于15,所以可以进行二进制枚举,枚举这个病存在或者不存在,1表示存在,0表示不存在。例如,若有1、2两种病,则十进制数为3(对应二进制数011)。

        首先在0~2D-1范围中枚举出1的个数等于k的二进制数,然后跟所有的牛的染病情况进行一一比较,若符合,则cnt++,找出其中值最大的。

       (2)源程序。

#include <stdio.h>

#include <string.h>

int main()

{

    int n,d,k;

    scanf("%d%d%d",&n,&d,&k);

    int a[1001][16],b[16];

    int i,j;

    for (i=0;i<n;i++)

    {

       scanf("%d",&a[i][0]);

            for (j=1;j<=a[i][0];j++)

                     scanf("%d",&a[i][j]);

    }

    int ans=0;

    for (i=0;i<(1<<d);i++)

    {

        int cnt=0;

        memset(b,0,sizeof(b));

        int t=i;

        j=1;

        while (t!=0)         // 计算i对应二进制数中1的个数

        {

            if (t&1){

                b[j]=1;  cnt++;

            }

            j++;

            t=t>>1;

        }

        if (cnt>k) continue;

        cnt=0;

        for (j=0;j<n;j++)

        {

            int p;

            for (p=1;p<=a[j][0];p++)

                if (b[a[j][p]]==0) break;

            if (p>a[j][0]) cnt++;

        }

        if (cnt>ans) ans=cnt;

    }

    printf("%d\n",ans);

    return 0;

}

posted on 2021-12-25 11:55  aTeacher  阅读(354)  评论(0编辑  收藏  举报