数学模型 商人过河问题 C++实现
问题描述
将过河问题抽象为一个数学问题,安全渡河即为一个多步决策问题,在安全的前提下,每一步都考虑船上的商人与随从人数情况。
决策问题通常从考虑状态,决策,状态转移方程入手。
状态
设 s k = ( x k , y k ) s_k=(x_k,y_k) sk=(xk,yk)表示第 k k k次渡河前此岸的商人数与随从数,显然有 x k , y k = 0 , 1 , 2 , 3 ; k = 1 , 2 , ⋯ x_k,y_k=0,1,2,3;k=1,2,\cdots xk,yk=0,1,2,3;k=1,2,⋯。
考虑安全的状态集合即 S = { ( x , y ) ∣ x = 0 , y = 0 , 1 , 2 , 3 ; x = 3 , y = 0 , 1 , 2 , 3 ; x = y = 1 , 2 } S=\{(x,y)|x=0,y=0,1,2,3;x=3,y=0,1,2,3;x=y=1,2\} S={(x,y)∣x=0,y=0,1,2,3;x=3,y=0,1,2,3;x=y=1,2}其中第一项表示商人全部在对岸,此岸无商人,故无法抢劫;第二项表示商人全部在此案,对岸无商人,亦无法抢劫;第三项表示两岸的商人与随从相等。
决策
设 d k = ( Δ x k , Δ y k ) d_k=(\Delta{x_k},\Delta{y_k}) dk=(Δxk,Δyk)表示第 k k k次渡河时船上的商人数与随从数,显然有 Δ x k , Δ y k = 0 , 1 , 2 ; k = 1 , 2 , ⋯ \Delta{x_k},\Delta{y_k}=0,1,2;k=1,2,\cdots Δxk,Δyk=0,1,2;k=1,2,⋯。
考虑允许的决策集合 D = { ( x , y ) ∣ x , y = 0 , 1 , 2 ; x + y = 1 , 2 } D=\{(x,y)|x,y=0,1,2;x+y=1,2\} D={(x,y)∣x,y=0,1,2;x+y=1,2}。故有五种不同的决策: ( 0 , 1 ) , ( 0 , 2 ) , ( 1 , 0 ) , ( 1 , 1 ) , ( 2 , 0 ) (0,1),(0,2),(1,0),(1,1),(2,0) (0,1),(0,2),(1,0),(1,1),(2,0)。
状态转移方程
即求解
s
k
+
1
=
f
(
s
k
,
d
k
)
s_{k+1}=f(s_k,d_k)
sk+1=f(sk,dk)。
当为奇数次划船时为从此岸到彼岸,故
s
k
+
1
=
s
k
−
d
k
s_{k+1}=s_k-d_k
sk+1=sk−dk;
当为偶数次划船时为从彼岸至此案,故
s
k
+
1
=
s
k
+
d
k
s_{k+1}=s_k+d_k
sk+1=sk+dk。
故状态转移方程为:
s
k
+
1
=
s
k
+
(
−
1
)
k
d
k
k
=
1
,
2
,
⋯
s_{k+1}=s_k+(-1)^kd_k\quad k=1,2,\cdots
sk+1=sk+(−1)kdkk=1,2,⋯故问题即为:求一系列
d
k
∈
D
(
k
=
1
,
2
,
⋯
,
n
)
d_k\in{D}(k=1,2,\cdots,n)
dk∈D(k=1,2,⋯,n),使
s
k
∈
S
s_k\in{S}
sk∈S且由
s
1
=
(
3
,
3
)
s_1=(3,3)
s1=(3,3)转移至
s
n
+
1
=
(
0
,
0
)
s_{n+1}=(0,0)
sn+1=(0,0)。
其中安全状态点集如下图绿点所示:
求解
此代码仅能模拟两组基础解(即11步的解),剩下两组可以根据对称性得出。
#include <iostream>
#include <vector>
using namespace std;
bool check[4][4]={ /*状态是否安全*/
{1,1,1,1},
{0,1,0,0},
{0,0,1,0},
{1,1,1,1}
};
int d[5][2] = {{0,1},{0,2},{1,0},{1,1},{2,0}}; /*决策*/
struct S
{
int x;
int y;
};
vector<S> v; /*记录路径*/
int count = 0;
int k = 1;
void ouput(){
for(int i=0; i<v.size(); i++){
cout<<i+1<<": "<<v[i].x<<" "<<v[i].y<<endl;
}
cout<<endl;
}
bool valid(int x, int y){ /*状态是否合法*/
return (0<=x && x<=3) && (0<=y && y<=3);
}
void solve(int x, int y, int way){ /*dfs函数*/
if(count == 5) /*只取前五种*/
return;
if(x==0 && y==0){ /*递归基*/
ouput();
count++;
return;
}
for(int i=0; i<5; i++){ /*5种不同决策*/
if(k%2==0){
x += d[i][0];
y += d[i][1];
}else{
x -= d[i][0];
y -= d[i][1];
}
if(valid(x,y)){
if(check[x][y] && way != i){ /*新状态安全且不等于原状态*/
S s;
s.x = x;s.y = y;
v.push_back(s);
k++;
solve(x, y, i); /*向下层搜索*/
//if(count % 2 == 0)
//i++;
v.pop_back(); /*置为原状态*/
k--;
if(k%2){
x += d[i][0];
y += d[i][1];
}else{
x -= d[i][0];
y -= d[i][1];
}
}else{ /*新状态不能选*/
if(k%2){
x += d[i][0];
y += d[i][1];
}else{
x -= d[i][0];
y -= d[i][1];
}
}
}else{ /*新状态不合法,置为原状态*/
if(k%2){
x += d[i][0];
y += d[i][1];
}else{
x -= d[i][0];
y -= d[i][1];
}
}
}
}
int main(){
solve(3,3,-1);
cout<<count<<endl;
return 0;
}
结果:
11 | 15 | 19 |
可以看到,在11步时,有两种情况,即其后半部分有两种情况,如下:
(
0
,
2
)
→
(
0
,
3
)
→
(
0
,
1
)
→
(
0
,
2
)
→
(
0
,
0
)
(
0
,
2
)
→
(
0
,
3
)
→
(
0
,
1
)
→
(
1
,
1
)
→
(
0
,
0
)
(0,2)\to(0,3)\to(0,1)\to(0,2)\to(0,0)\\ (0,2)\to(0,3)\to(0,1)\to(1,1)\to(0,0)
(0,2)→(0,3)→(0,1)→(0,2)→(0,0)(0,2)→(0,3)→(0,1)→(1,1)→(0,0)而在步数增加时,会形成局部的循环,如紫色框所示,故在前半部分决策时只能模拟一种情况,即
(
3
,
3
)
→
(
3
,
1
)
→
(
3
,
2
)
→
(
3
,
0
)
→
(
3
,
1
)
(3,3)\to(3,1)\to(3,2)\to(3,0)\to(3,1)
(3,3)→(3,1)→(3,2)→(3,0)→(3,1)根据对称性即式
(
2
)
(2)
(2),不难得出前半部分另一种决策为:
(
3
,
3
)
→
(
2
,
2
)
→
(
3
,
2
)
→
(
3
,
0
)
→
(
3
,
1
)
(3,3)\to(2,2)\to(3,2)\to(3,0)\to(3,1)
(3,3)→(2,2)→(3,2)→(3,0)→(3,1)故基础的决策情况即11步时,有4组基本解。