题解 P1002 【过河卒】
为什么大家写的全是递推!!!
我一定要写一篇不是递推的题解!!!
Updated on 2022.7.14:添加
这里我介绍两种方法。
矩阵乘法
由于数据很小,
如果我们给网格图内的点标号,记从
我们惊奇的发现,这就是矩阵乘法的定义。
因此,我们连接所有互相可达的点,进行矩阵快速幂即可。
#include <bits/stdc++.h>
#define ll long long
#define rint register int
using namespace std;
int a[22][22][22][22];
ll tot[22][22][22][22];
int n,m,x,y;
int judge(int p,int q)//判断p,q是否可走
{
if(p==x && q==y)
return 0;
int px=abs(p-x);
int qy=abs(q-y);
int mi=min(px,qy);
int mx=max(px,qy);
if(mi==1 && mx==2)
return 0;
return 1;
}
ll tmp[22][22][22][22]={0};
void ta()//将tot乘上a
{
for(int i1=0;i1<=n;i1++)
for(int i2=0;i2<=m;i2++)
for(int j1=i1;j1<=n;j1++)//如果j在i号点左侧,答案必然为0
for(int j2=i2;j2<=m;j2++)//同理
for(int k1=i1;k1<=j1;k1++)//k点必然在i和j之间
for(int k2=i2;k2<=j2;k2++)
tmp[i1][i2][j1][j2]+=tot[i1][i2][k1][k2]*a[k1][k2][j1][j2];
memcpy(tot,tmp,sizeof tmp);//复制回去
}
void tt()//将tot平方
{
for(int i1=0;i1<=n;i1++)
for(int i2=0;i2<=m;i2++)
for(int j1=i1;j1<=n;j1++)
for(int j2=i2;j2<=m;j2++)
for(int k1=i1;k1<=j1;k1++)
for(int k2=i2;k2<=j2;k2++)
tmp[i1][i2][j1][j2]+=tot[i1][i2][k1][k2]*tot[k1][k2][j1][j2];
memcpy(tot,tmp,sizeof tmp);
}
void pw(int ci)//求a的ci次方
{
if(ci==0)
return;
pw(ci>>1);
tt();
if(ci&1)
ta();
}
int main()
{
cin>>n>>m>>x>>y;
//连接竖向边
for(int i=0;i<n;i++)
for(int j=0;j<=m;j++)
if(judge(i,j))
a[i][j][i+1][j]=1;
//连接横向边
for(int i=0;i<=n;i++)
for(int j=0;j<m;j++)
if(judge(i,j))
a[i][j][i][j+1]=1;
//矩阵的单位元
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)
tot[i][j][i][j]=1;
pw(n+m);//由于要走的步数是一定的,所以就是a^(n+m)
cout<<tot[0][0][n][m];
return 0;
}
小学奥数
前置芝士:
- 从
(0,0) 走到(m,n) 的最短路数量=C^m_{m+n} - 从
(0,0) 经过(x,y) 到达(m,n) 的最短路数量=C^x_{x+y}C^{m-x}_{m+n-x-y} (即芝士 1 配合乘法原理的结果)
如果您了解了上面的内容,那么我们就先以样例画一张图:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| S | . | . | . | . | . | . |
| . | . | x | . | x | _ | . |
| . | x | . | . | _ | x | . |
| . | . | . | _x | . | . | . |
| . | x | _ | . | . | x | . |
| . | _ | x | . | x | . | . |
| . | . | . | . | . | . | E |
x 表示不可以走的地方。
我们分情况讨论。
1.不经过特殊格子的
我们称下划线所在的格子为特殊格子。显然这五个格子最多经过1个,所以我们只要求出总共的方法数-经过这五个格子的方法数即可求出。也就是求
2.经过 (x-2,y+2) 的
这种路径只有一种情况:先到达
方法数为
3.经过 (x+2,y-2) 的
同理,方法数为
4.经过 (x-1,y+1) 的
要经过这个点,需要从
到达
5.经过 (x+1,y-1) 的
与第 4 种情况同理,方案数为
完结撒花!!!
等等,感觉有点问题...
有一种特殊情况:如果起点或终点离马的位置太近,那么上面的分类讨论的第 4 种和第 5 种情况就会出现问题——可能起点或终点本来就在马的攻击范围内。
所以,遇到这种情况别忘记特判。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll C(int m,int n)
{
if(n<0 || m<0 || n<m)
return 0;
ll tot=1;
for(int i=1;i<=m;i++)
{
tot*=n-i+1;
tot/=i;
}
return tot;
}
int n,m,x,y;
int rt[5][5]={
0,0,1,0,0,
0,0,1,1,0,
0,0,0,1,1};//我们记录下哪些节点作为起点时,可以从右边的口子出去,其中(2,2)为马的位置
int dw[5][5]={
{},
{},
1,1,0,0,0,
0,1,1,0,0,
0,0,1,0,0};//同理,哪些可以从下边口子出去
int main()
{
cin>>n>>m>>x>>y;
if(n-x<3 && m-y<3)
{
x=n-x;
y=m-y;
}//为了不想把特判代码写两遍,如果终点离得太近就旋转180度,变成起点离得太近的情况
if(x<3 && y<3)
{
int nx=2-x;
int ny=2-y;//起点相对于马的坐标而言的位置
ll tot=0;
tot+=C(n-x,m+n-x-y-3)*rt[nx][ny];//右边出去的方法数
tot+=C(n-x-3,m+n-x-y-3)*dw[nx][ny];//同理
cout<<tot;
return 0;
}
ll tot=C(n,n+m);
for(int i=-2;i<=2;i++)
tot-=C(x-i,x+y)*C(n-x+i,n+m-x-y);
//情况1
tot+=C(x+2,x+y-1)*C(n-x-3,n+m-x-y-1);//情况2
tot+=C(x-3,x+y-1)*C(n-x+2,n+m-x-y-1);//情况3
tot+=C(x,x+y-3)*C(n-x-3,n+m-x-y-3);//情况4
tot+=C(x-3,x+y-3)*C(n-x,n+m-x-y-3);//情况5
cout<<tot;
return 0;
}
完结撒花!!!