[NOIP2002 普及组] 过河卒
题目链接 https://www.luogu.com.cn/problem/P1002
一道入门的dp问题。
初始位置为(0,0),有点儿麻烦,改为(1,1),所以所有坐标的位置全部+1。
卒到达的每一个位置都是从此位置的上方和左方走过来的。那么假设卒从(1,1)到达此点的左方点的路径有x条,到达此点的上方点的路径有y条,则到达此点的路径有x+y条。
那么可以得到状态转移方程:dp[i][j]=dp[i-1][j]+dp[i][j-1],所以最终的答案被储存在dp[n][m](假设B的坐标为(n,m))。
需要注意的是需要对dp[1][1]或dp[2][0]进行初始化=1,否则无论走到哪个点其结果都是0。(令dp[2][1]=1按道理也是可以的,我也是这么写的)。
接下来开始循环计算到达每个点有几条路径。当遇到马和马能到达的点时就跳过。在这之前需要将马所在的点和马能到达的位置初始化,用来判断。
放AC代码
1 #include<bits/stdc++.h> 2 using namespace std; 3 int bx,by,mx,my; 4 long long int dp[40][40]; 5 int s[40][40]; 6 int main() 7 { 8 cin>>bx>>by>>mx>>my; 9 bx+=2; by+=2; mx+=2; my+=2;//防止越界,并且假设A点为(1,1) 10 dp[2][1]=1; 11 s[mx][my]=1;//标记马的位置以及马能到达的点 12 s[mx-2][my-1]=1; s[mx-2][my+1]=1; 13 s[mx-1][my-2]=1; s[mx-1][my+2]=1; 14 s[mx+1][my-2]=1; s[mx+1][my+2]=1; 15 s[mx+2][my-1]=1; s[mx+2][my+1]=1; 16 for(int i=2;i<=bx;i++) 17 { 18 for(int j=2;j<=by;j++) 19 { 20 if(s[i][j]) continue;//如果此点被标记则跳过 21 dp[i][j]=dp[i-1][j]+dp[i][j-1];//状态转移方程 22 } 23 } 24 cout<<dp[bx][by]; 25 return 0; 26 }
当然,还可以优化。用滚动数组可以节省空间。
因为我们只用到第i行和第i-1行的答案,所以可以只定义dp[2][1]就好了。
如何只保留第i和第i-1行的答案?取模。i=>i%2,i-1=>(i-1)%2。
此外,因为是滚动数组 , 所以如果当前位置被马拦住了一定要记住清零。
另,学到了一个新知识:
x%2可以在代码中写成更快的运算方式x&1。
如果x是偶数,那么x&1=0,如果x是奇数,那么x&1=1。
测试了一下:
拿给出的第五个测试点来说,即输入14 16 7 5(我怎么知道的?蛤蛤,因为我又出现鲨臂错误啦!)。
用codeblocks跑出来:%2为3.348s;&1为3.117s。(um...一点点也是快啦)
放AC代码
1 #include<bits/stdc++.h> 2 using namespace std; 3 int bx,by,mx,my; 4 long long dp[2][40]; 5 int s[40][40]; 6 int main() 7 { 8 cin>>bx>>by>>mx>>my; 9 bx+=2; by+=2; mx+=2; my+=2; 10 dp[1][2]=1; 11 s[mx][my]=1; 12 s[mx-2][my-1]=1; s[mx-2][my+1]=1; 13 s[mx-1][my-2]=1; s[mx-1][my+2]=1; 14 s[mx+1][my-2]=1; s[mx+1][my+2]=1; 15 s[mx+2][my-1]=1; s[mx+2][my+1]=1; 16 for(int i=2;i<=bx;i++) 17 { 18 for(int j=2;j<=by;j++) 19 { 20 if(s[i][j]) 21 { 22 dp[i&1][j]=0; 23 continue; 24 } 25 dp[i&1][j]=dp[(i-1)&1][j]+dp[i&1][j-1]; 26 } 27 } 28 cout<<dp[bx&1][by]; 29 return 0; 30 }