例38   涂国旗

题目描述

某国法律规定,只要一个由 N×M 个小方块组成的旗帜符合如下规则,就是合法的国旗。

从最上方若干行(至少一行)的格子全部是白色的;

接下来若干行(至少一行)的格子全部是蓝色的;

剩下的行(至少一行)全部是红色的;

现有一个棋盘状的布,分成了 N 行 M 列的格子,每个格子是白色蓝色红色之一,小 a 希望把这个布改成该国国旗,方法是在一些格子上涂颜料,盖住之前的颜色。

小a很懒,希望涂最少的格子,使这块布成为一个合法的国旗。

输入格式

第一行是两个整数 N,M。

接下来 N 行是一个矩阵,矩阵的每一个小方块是W(白),B(蓝),R(红)中的一个。

输出格式

一个整数,表示至少需要涂多少块。

输入样例

4 5

WRWRW

BWRWB

WRWRW

RWBWR

输出样例

11

         (1)编程思路。

        定义数组int  w[51],b[51],r[51];数组元素w[i]、b[i]和r[i]分别表示把前i行全部涂成白色、蓝色或红色所需要涂的格子数。

        设蓝色所在的行是第i行到第j行,则第1行到第i-1行是白色,第j+1行到第n行是红色,此时需要涂的格子数为w[i-1]+b[j]-b[i-1]+r[n]-r[j]。

        对蓝色所在的行i~j进行穷举,显然2≤i≤n-1,i≤j≤n-1。对穷举的每种情况找出所需要涂的格子数(w[i-1]+b[j]-b[i-1]+r[n]-r[j])的最小值即可。

         (2)源程序。
#include <stdio.h>
int main()
{
    int w[51]={0},b[51]={0},r[51]={0};
    int n,m;
    scanf("%d%d",&n,&m);
    char s[51];
    int i,j;
    for (i=1;i<=n;i++)
    {
        scanf("%s",s);
        for (j=0;j<m;j++)
        {
            if (s[j]!='W') w[i]++;
            if (s[j]!='B') b[i]++;
            if (s[j]!='R') r[i]++;
        }
        w[i]+=w[i-1];
        b[i]+=b[i-1];
        r[i]+=r[i-1];
    }
    int ans=50*50;
    for (i=2;i<=n-1;i++)
        for (j=i;j<=n-1;j++)
        {
            int t;
            t=w[i-1]+b[j]-b[i-1]+r[n]-r[j];
           if (ans>t) ans=t;
        }
    printf("%d\n",ans);
         return 0;
}

习题38

38-1  健康的奶牛

题目描述

农民 John 以拥有世界上最健康的奶牛为傲。他知道每种饲料中所包含的牛所需的最低的维他命量是多少。请你帮助农夫喂养他的牛,以保持它们的健康,使喂给牛的饲料的种数最少。

给出牛所需的最低的维他命量,输出喂给牛需要哪些种类的饲料,且所需的饲料剂量最少。

维他命量以整数表示,每种饲料最多只能对牛使用一次,数据保证存在解。

输入格式

第一行一个整数 v(1≤v≤25),表示需要的维他命的种类数。

第二行 v 个整数,表示牛每天需要的每种维他命的最小量。

第三行一个整数 g(1≤g≤15),表示可用来喂牛的饲料的种数。

下面 g 行,第 n 行表示编号为 n 饲料包含的各种维他命的量的多少。

输出格式

输出文件只有一行,包括牛必需的最小的饲料种数 p;后面有 p 个数,表示所选择的饲料编号(按从小到大排列)。

如果有多个解,输出饲料序号最小的(即字典序最小)。

输入样例

4

100 200 300 400

3

50  50  50  50

200 300 200 300

900 150 389 399

输出样例

2 1 3

         (1)编程思路。

        g种饲料,每种有选用和不选用两种状态。一共有2g种状态,因为g最多为15,因此状态量最多为215,可以用二进制位运算枚举g种饲料的各种状态组合,检查每种状态组合是否满足牛所需的最低的维他命量,并进行相应的更新处理。

         例如,当g=3时,有3种饲料(设为a,b,c这3种),8种组合状态,其中0(000)表示三种饲料均不选用,1(001)表示只选用饲料c,2(010)表示只选用饲料b,3(011)表示选用饲料b和c,4(100)表示选用饲料a,…,7(111)表示3种饲料全部选用。

        (2)源程序。

#include <stdio.h>
int main()
{
    int v,g;
    scanf("%d",&v);
    int a[26];
    int i,j,k;
    for (i=0;i<v;i++)
       scanf("%d",&a[i]);
    scanf("%d",&g);
    int map[16][26];
    for (i=0;i<g;i++)
      for (j=0;j<v;j++)
         scanf("%d",&map[i][j]);
    int cnt=15,num;
    for (k=1;k<(1<<g);k++)      //  枚举所有状态
    {
       int lowbit=k;
       int b[26];
       for (i=0;i<v;i++)
           b[i]=0;
       int p=0;
       for (i=0; lowbit>0; i++,lowbit>>=1)
          if (lowbit&1)
          {
              p++;
              for (j=0;j<v;j++)
              {
                  b[j]+=map[i][j];
              }
           }
       for (i=0;i<v;i++)
            if (a[i]>b[i]) break;
       if (i>=v && p<cnt)
       {
          cnt=p;
          num=k;
       }
    }
    printf("%d ",cnt);
    for (i=1; num>0;i++,num>>=1)
       if (num&1)
          printf("%d ",i);
    printf("\n");
    return 0;
}
38-2  海明码

题目描述

给出 n、b、d,要求找出n个由 0、1组成的编码,每个编码有b位,使得两两编码之间至少有 d个单位的 “Hamming距离”。

“Hamming距离”是指对于两个编码,它们二进制表示法中的不同二进制位的数目。看下面的两个编码 0x554 和 0x234(十六进制数)

0x554 = 0101 0101 0100

0x234 = 0010 0011 0100

不同位     xxx    xx

因为有五个位不同,所以“Hamming距离”是 5。

输入格式

一行,包括 n,b,d。1≤n≤64,1≤b≤8,1≤d≤7。

输出格式

n 个编码(用十进制表示),要排序,十个一行。

如果有多解,你的程序要输出这样的解:假如把它化为 2b进制数,它的值要最小。

输入样例

16 7 3

输出样例

0 7 25 30 42 45 51 52 75 76

82 85 97 102 120 127

         (1)编程思路。

        由于二进制数位的异或运算具有一个特点:相同的数位异或为0,不同的数位异或为1。因此,两个数的“Hamming距离”实际就是两个数异或后,结果中二进制数位“1”的个数。

定义数组int a[256];存放满足“Hamming距离”大于或等于d的数。初始时a[0]=0,表示0是保存的第1个数。

        用循环对1~2b-1之间的每个整数进行穷举,将穷举到的当前数i和a数组中已经保存的所有数据进行比较,若i分别与已保存的每个数异或的结果中“1”的个数均不小于d,则保存数据i。

      (2)源程序。

#include <stdio.h>
int bitCount(int x)
{
    int cnt=0;
    while (x!=0)
    {
        cnt++;
        x=x&(x-1);
    }
    return cnt;
}
int main(void)
{
    int n,b,d;
    scanf("%d%d%d",&n,&b,&d);
    int a[256];
    int cnt=1;
    a[0]=0;
    int i,j;
    for (i=1;i<(1<<b);i++)
    {
        for (j=0;j<cnt;j++)
            if (bitCount(i^a[j])<d) break;
        if(j>=cnt)
            a[cnt++] = i;
        if (cnt == n)
            break;
    }
    for (i=0;i<cnt;i++)
    {
        printf("%d ",a[i]);
        if ((i+1)%10==0)
            printf("\n");
    }
    return 0;
}
38-3  彩弹游戏

题目描述

奶牛们最近从玩具商那里,买来了一套仿真版彩弹游戏设备(类似于真人 CS)。Bessie 把她们玩游戏的草坪划分成了 N×N 的矩阵(1≤N≤100),同时她算出了她的 K 个对手在草地上的位置(1≤K≤105),现在你需要帮 Bessie 算些东西。

在这个游戏中,奶牛们用一把枪向八个方向中的任意一个方向射出子弹,这八个方向分别是:正北,正南,正东,正西,东北,东南,西北,西南(东北指北偏东45∘,东南,西北,西南同理)。

Bessie 想要你算出,有多少个位置可以让她射到所有对手。特别地,Bessie 可以和她的某一个对手站在同一格子,这时候她可以射到和她同一格子的对手。

输入格式

第一行两个整数 N,K。

接下来 K 行,每行两个整数 Ri和 Ci,表示第 i 头奶牛在第Ri行第Ci列。可能有两个奶牛在同一位置上。

输出格式

输出 Bessie 可以选择的格子数目。

输入样例

4 3

2 1

2 3

4 1

输出样例

5

        (1)编程思路。

        用二重循环对(1,1)~(N,N)这N2个位置进行穷举,若某个位置能射到所有对手,则计数。

       (2)源程序。

#include <stdio.h>
int abs(int a)
{
    return a>=0?a:-a;
}
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    int i,x,y;
    int tot=0,ans=0;
    int v[101][101]={0};
    int a[10001][2];
    for (i=1;i<=k;i++)
    {
        scanf("%d%d",&x,&y);
        if (!v[x][y])
        {
           v[x][y]=1;
           a[tot][0]=x;
           a[tot][1]=y;
           tot++;
        }
    }
    for (x=1;x<=n;x++)
      for (y=1;y<=n;y++)
      {
          int f=1;
          for (i=0;i<tot;i++)
          {
             int dx=abs(x-a[i][0]);
             int dy=abs(y-a[i][1]);
             if (dx!=dy && dx!=0 && dy!=0)
             {
                 f=0;
                 break;
             }
          }
          ans+=f;
        }
    printf("%d\n",ans);
    return 0;
}