C語言程序設計100例之(20):過河卒

例20  過河卒

題目描述算法

如圖1,在棋盤的A點有一個過河卒,須要走到目標B點。卒行走規則:能夠向下、或者向右。同時在棋盤上的任一點有一個對方的馬(如圖1的C點),該馬所在的點和全部跳躍一步可達的點稱爲對方馬的控制點。例如,圖1中C點上的馬能夠控制9個點(圖中的P1,P2,…,P8 和C)。卒不能經過對方馬的控制點。編程

棋盤用座標表示,A點(0,0)、B點(n,m)(n,m爲不超過50的整數,並由鍵盤輸入),一樣馬的位置座標經過鍵盤輸入,並約定C<>A,同時C<>B。數組

編寫程序,計算出卒從A點可以到達B點的路徑的條數。spa

   圖1  棋盤上的過河卒和對方的控制馬3d

輸入格式blog

一行四個數據,分別表示B點座標和馬的座標。進程

輸出格式ci

一個數據,表示全部的路徑條數。io

輸入樣例table

6 6 3 3

輸出樣例

6

        (1)編程思路。

        在棋盤的A點(0,0)的過河卒要到達棋盤上的任意一點,只能從左邊和上邊兩個方向過來。所以,要到達某一點的路徑數,等於和它相鄰的左、上兩個點的路徑數和:

        F[i][j] = F[i-1][j] + F[i][j-1]。

        可使用逐列(或逐行)遞推的方法來求出從起始頂點到終點的路徑數目,即便有障礙(將馬的控制點稱爲障礙),這一遞推方法也徹底適用,只要將到達該點的路徑數目設置爲0便可,用F[i][j]表示到達點(i,j)的路徑數目,g[i][j]表示點(i,j)有無障礙,遞推方程以下:

        F[0][0] = 1     初始點直接可達。

        F[i][0] = F[i-1][0]   (i > 0,g[i][0] =0)     // 左邊界

        F[0][j] = F[0][j-1]   (j > 0,g[0][j] = 0)    // 上邊界

        F[i][j] = F[i-1][j] + F[i][j-1]   (i > 0,j > 0,g[x, y] = 0) // 遞推式

        (2)源程序。

#include <stdio.h>

int main()

{

    int i,j,x,y,n,m,forbidden[51][51]; 

    long int  ans[51][51]; 

    int dx[8]={-2,-1,1,2,2,1,-1,-2};

    int dy[8]={1,2,2,1,-1,-2,-2,-1};

    scanf("%d%d%d%d",&n,&m,&x,&y); 

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

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

        {

            forbidden[i][j]=0;

            ans[i][j]=0;

        }

    ans[0][0]=1; 

    forbidden[x][y]=1;

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

            if (x+dx[i]>=0 && x+dx[i]<=n && y+dy[i]>=0 && y+dy[i]<=m)

                   forbidden[x+dx[i]][y+dy[i]]=1;

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

        if (forbidden[i][0]==0) 

            ans[i][0]=1; 

        else break; 

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

        if (forbidden[0][i]==0) 

            ans[0][i]=1; 

        else break; 

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

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

            if (forbidden[i][j]==0) 

                ans[i][j]=ans[i-1][j]+ans[i][j-1]; 

    printf("%ld\n",ans[n][m]);

    return 0; 

}

習題20

20-1  馬的行走路徑

問題描述

設有一個n*m的棋盤(2<=n<=50,2<=m<=50),在棋盤上任一點有一箇中國象棋馬,如圖2(a)所示。馬行走的規則爲:(1)馬走日字;(2)馬只能向右走,即如圖2(b)所示的4種走法。

編寫一個程序,輸入n和m,找出一條馬從棋盤左下角(1,1)到右上角(n,m)的路徑。例如:輸入n=四、m=4時,輸出路徑 (1,1)->(2,3)->(4,4)。這一路經如圖2(c)所示。若不存在路徑,則輸出"No!"

   

 圖2  棋盤及馬兒的行走

輸入格式

一行兩個數據,表示終點的位置座標。

輸出格式

一條可行的行走路徑。若是可行的行走路徑有多條,任意輸出一條便可。若不存在路徑,則輸出"No!"。

輸入樣例

10 10

輸出樣例

(1,1)->(2,3)->(3,5)->(4,7)->(5,9)->(6,7)->(7,9)->(9,8)->(10,10)

        (1)編程思路

        先將棋盤的橫座標規定爲i,縱座標規定爲j,對於一個n×m的棋盤,i的值從1到n,j的值從1到m。棋盤上的任意點均可以用座標(i,j)表示。

        對於馬的移動方法,用變量k來表示四種移動方向(一、二、三、4);而每種移動方法用偏移值來表示,並將這些偏移值分別保存在數組dx和dy中,如表1所示。

表1  4種移動方法對應偏移值

K

Dx[k]

Dy[k]

1

2

1

2

2

-1

3

1

2

4

1

-2

        根據馬走的規則,馬能夠由(i-dx[k],j-dy[k])走到(i,j)。只要馬能從(1,1)走到(i-dx[k],j-dy[k]),就必定能走到(i,j),固然,馬走的座標必須保證在棋盤上。

        以(n,m)爲起點向左遞推,當遞推到(i-dx[k],j-dy[k])的位置是(1,1)時,就找到了一條從(1,1)到(n,m)的路徑。

        程序中可用一個二維數組a表示棋盤,使用倒推法,從終點(n,m)往左遞推,設初始值a[n][m]爲(-1,-1)(表示終點),若是從(i,j)一步能走到(n,m),就將(n,m)存放在a[i][j]中。如表2所示,a[3][2]和a[2][3]的值是(4,4),表示從這兩個點均可以到達座標(4,4)。從(1,1)可到達(2,3)、(3,2)兩個點,因此a[1][1]存放兩個點中的任意一個便可。遞推結束之後,若是a[1][1]值爲(0,0)說明不存在路徑,不然a[1][1]值就是馬走下一步的座標,以此順推輸出路徑。

表2  N=4,M=4時,數組a的賦值狀況

 

 

 

A[4][4]={-1,-1}

 

A[2][3]={4,4}

 

 

 

 

A[3][2]={4,4}

 

A[1][1]={2,3}

 

 

 

        (2)源程序。

#include <stdio.h>

int main()

{

    int dx[5]={0,2,2,1,1},dy[5]={0,1,-1,2,-2};

    struct point

     {

            int x;

            int y;

     };

    struct point a[51][51];

    int i,j,n,m,k;

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

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

                      a[i][j].x=a[i][j].y=0;

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

    a[n][m].x=-1;          // 標記爲終點

    a[n][m].y=-1;

    for (i=n;i>=2;i--)       // 倒推

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

       if (a[i][j].x!=0)

        for (k=1;k<=4;k++)

        {

                 a[i-dx[k]][j-dy[k]].x=i;

                a[i-dx[k]][j-dy[k]].y=j;

        }

         if (a[1][1].x==0)

                   printf("No!\n");

        else                 // 存在路徑

       {

              i=1;  j=1;

             printf("(%d,%d)",i,j);

             while (a[i][j].x!=-1)

            {

                k=i;

                i=a[i][j].x;  j=a[k][j].y;

                printf("->(%d,%d)",i,j);

            }

            printf("\n");

         }

         return 0;

}

20-2  方格取數(一)

題目描述

設有N×N的方格圖(N≤9),咱們將其中的某些方格中填入正整數,而其餘的方格中則放入數字0。以下所示(見樣例):

某人從圖的左上角的A點出發,能夠向下行走,也能夠向右走,直到到達右下角的B點。在走過的路上,他能夠取走方格中的數(取走後的方格中將變爲數字0)。

此人從A點走到B點,試找出1條這樣的路徑,使得取得的數之和爲最大。

輸入格式

輸入的第一行爲一個整數N(表示N×N的方格圖),接下來的每行有三個整數,前兩個表示位置,第三個數爲該位置上所放的數。一行單獨的0表示輸入結束。

輸出格式

只需輸出一個整數,表示找出的1條路徑上取得的最大的和。

輸入樣例

8

2 3 13

2 6 6

3 5 7

4 4 14

5 2 21

5 6 4

6 3 15

7 2 14

0 0 0

輸出樣例

36

        (1)編程思路。

        由於行走的方向是:能夠向下行走,也能夠向右走。所以,位置(i,j)能夠由上邊的格子(i-1,j)走到,也能夠由左邊的格子(i,j-1)走到。

        設f[i][j]表示走到格子(i,j)處所取方格數的最大值,a[x][y]表示格子(x,y)上的數字。顯然有

        f[i][j]=max(f[i-1][j],f[i][j-1])+a[i][j];

        初始時   f[1][1]=a[1][1]。

       (2)源程序。

#include <stdio.h>

int max(int a,int b)

{return a<b?b:a;}

int main()

{

    int f[10][10]={0}, a[10][10]={0};

    int n;

    scanf("%d",&n);

   while(1)

    {

                   int x, y, w;

                   scanf("%d%d%d",&x,&y,&w);

                   if (x==0 && y==0 && w==0) break;

                   a[x][y]=w;

    }

    f[1][1]=a[1][1];

    int i, j;

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

     {

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

                 {

                        f[i][j]=max(f[i-1][j],f[i][j-1])+a[i][j];

                   }

     }

     printf("%d\n",f[n][n]);

    return 0;

}

20-3  方格取數(二)

題目描述

設有N×N的方格圖(N≤9),咱們將其中的某些方格中填入正整數,而其餘的方格中則放入數字0。以下所示(見樣例):

某人從圖的左上角的A點出發,能夠向下行走,也能夠向右走,直到到達右下角的B點。在走過的路上,他能夠取走方格中的數(取走後的方格中將變爲數字0)。

此人從A點到B點共走兩次,試找出2條這樣的路徑,使得取得的數之和爲最大。

輸入格式

輸入的第一行爲一個整數N(表示N×N的方格圖),接下來的每行有三個整數,前兩個表示位置,第三個數爲該位置上所放的數。一行單獨的0表示輸入結束。

輸出格式

只需輸出一個整數,表示2條路徑上取得的最大的和。

輸入樣例

8

2 3 13

2 6 6

3 5 7

4 4 14

5 2 21

5 6 4

6 3 15

7 2 14

0 0 0

輸出樣例

67

        (1)編程思路1。

        本題要求找到2條從(1,1)到(n,n)的路徑,被取走的格子裏的數變爲0,使得在兩條路徑上格子中數之和最大時,就成爲了「二取方格數」問題。

        最容易想到的就是前後作兩次單條路徑「方格取數」,這一算法的本質是貪心,但這是錯誤的,反例以下:

3

4 

5

0 

0 

0

2 

8 

2

        貪心:第一路徑:3->4->8->2  (17)    第二路徑:5 (5)   總和爲22 

        事實上咱們能夠將全部的數都取完,總和爲24。 

        解決「二取方格數」問題須要用到「多進程DP」。即解決本題時,因爲此人從A點到B點共走兩次,要找出2條這樣的路徑,所以能夠考慮爲兩我的同時從A走到B。

設f[i][j][k][l]爲第一我的走到(i,j),第二我的走到(k,l)時方格取數能達到的最大值,a[x][y]表示格子(x,y)上的數字。

狀態轉移狀況以下:

1)兩我的同時向右走

f[i][j][k][l]=max(f[i][j][k][l],f[i-1][j][k-1][l]+a[i][j]+a[k][l]);

2)兩我的同時向下走

f[i][j][k][l]=max(f[i][j][k][l],f[i][j-1][k][l-1]+a[i][j]+a[k][l]);

3)兩我的分別向右和向下走

f[i][j][k][l]=max(f[i][j][k][l],f[i-1][j][k][l-1]+a[i][j]+a[k][l]);

4)兩我的分別向下和向右走

f[i][j][k][l]=max(f[i][j][k][l],f[i][j-1][k-1][l]+a[i][j]+a[k][l]);

固然,若兩人走到了同一個格子,即(i,j)和(k,l)是同一個點,f[i][j][k][l]值還要減去a[i][j]。

       兩我的都走到(n,n)格子時,獲得答案,即f[n][n][n][n]爲所求。

     (2)源程序1。

#include <stdio.h>

int max(int a,int b)

{return a<b?b:a;}

int main()

{

    int f[10][10][10][10]={0}, a[10][10]={0};

    int n;

    scanf("%d",&n);

    while(1)

    {

                   int x, y, w;

                   scanf("%d%d%d",&x,&y,&w);

                   if (x==0 && y==0 && w==0) break;

                   a[x][y]=w;

    }

    f[1][1][1][1]=a[1][1];

    int i, j, k, l;

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

     {

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

                          for (k=1;k<=n;k++)

                                     for (l=1;l<=n;l++)

                                     {

                                               f[i][j][k][l]=max(f[i][j][k][l],f[i-1][j][k-1][l]+a[i][j]+a[k][l]);

                                               f[i][j][k][l]=max(f[i][j][k][l],f[i][j-1][k][l-1]+a[i][j]+a[k][l]);

                                               f[i][j][k][l]=max(f[i][j][k][l],f[i-1][j][k][l-1]+a[i][j]+a[k][l]);

                                               f[i][j][k][l]=max(f[i][j][k][l],f[i][j-1][k-1][l]+a[i][j]+a[k][l]);

                                               if(i==k && j==l)f[i][j][k][l]-=a[i][j];

                                     }

     }

     printf("%d\n",f[n][n][n][n]);

    return 0;

}

        (3)編程思路2。

        按思路1的方法,因爲狀態總共有n^4種,因此時間複雜度爲O(n^4)。

        若是讓兩我的同時從(1,1)處出發,並同時向前延伸,那麼當兩我的都走了k步,兩條路徑都已經各自包含k個方格時,兩條路徑的末端必同在整個矩陣的第k條對角線上。以下圖3所示。

圖3  行走對角線示意圖

        由圖3可知,走1步可到達(1,1)格子(標註爲2),走兩步可到達(1,2)或(2,1)格子(標註爲2),走三步可到達(1,3)、(2,2)或(3,1)格子(標註爲4),……。

         由圖可知,對於每一條路徑,向右延伸的格子數+向下延伸的格子數=k(定值),也就是末端兩個格子的縱橫座標之和=k。

        因此咱們只須要知道兩路徑末端所在的行編號x1,x2以及兩末端所在對角線編號k,就能夠肯定末端節點的位置(x1,k-x1),(x2,k-x2)。這樣,能夠只枚舉對角線、x1和x2。

        設狀態f[l][x1][x2]第一我的橫座標爲x1(即第一個路徑末端在第x1行),第二我的橫座標爲x2(即第二路徑末端在第x2行),且兩末端同在第k條對角線上時的最優解。

        到達狀態f[l][x1][x2]有有4種可能:

1)第1人從x1的左邊向右到達x1,第2人從x2的左邊向右到達x2,其前一狀態應爲f[k-1][x1-1][x2-1];

2)第1人從x1的上邊向下到達x1,第2人從x2的上邊向下到達x2,其前一狀態應爲f[k-1][x1][x2];

3)第1人從x1的左邊向右到達x1,第2人從x2的上邊向下到達x2,其前一狀態應爲f[k-1][x1-1][x2];

4)第1人從x1的上邊向下到達x1,第2人從x2的左邊向右到達x2,其前一狀態應爲f[k-1][x1][x2-1];

這樣,能夠獲得狀態轉移方程:

tmp=max(max(f[k-1][x1-1][x2-1],f[k-1][x1][x2]),max(f[k-1][x1-1][x2],f[k-1][x1][x2-1]));

f[k][x1][x2]=max(f[k][x1][x2],tmp+a[x1][k-x1]+a[x2][k-x2]);

一樣,若是點(x1,k-x1)和(x2,k-x2)重合了,須要減去一個點中的數(每一個點只能取一次)。

       (4)源程序2。

#include <stdio.h>

int max(int a,int b)

{return a<b?b:a;}

int main()

{

    int f[19][10][10]={0}, a[10][10]={0};

    int n;

    scanf("%d",&n);

    while(1)

    {

                   int x, y, w;

                   scanf("%d%d%d",&x,&y,&w);

                  if (x==0 && y==0 && w==0) break;

                  a[x][y]=w;

     }

    int d=n*2;

    f[2][1][1]=a[1][1];

    for (int i=3;i<=d;i++)

    {

                   int c=i<n+1?i:n+1;

                   int s=i>n?i-n:1;

                   for(int j=s;j<c;j++)

                            for(int k=s;k<c;k++)

                            {

                                     int x1=j,x2=k,y1=i-j,y2=i-k;

                                     int tmp=max(max(f[i-1][x1-1][x2-1],f[i-1][x1][x2]),

                                     max(f[i-1][x1-1][x2],f[i-1][x1][x2-1]));

                                     f[i][x1][x2]=max(f[i][x1][x2],tmp+a[x1][y1]+a[x2][y2]);

                                     if (x1==x2&&y1==y2)  f[i][x1][x2]=f[i][x1][x2]-a[x1][y1];

                            }

    }

   printf("%d\n",f[d][n][n]);

    return 0;

相關文章
相關標籤/搜索