題目描述算法
如圖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
在棋盤的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) // 遞推式
#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;
}
問題描述
設有一個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;
}
題目描述
設有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;
}
題目描述
設有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;
}