和吴昊一起玩推理 Round 5 —— 战车问题(蒙特卡洛实战)
如图所示,以上是一个N*N的方格,上面布满了堡垒和坦克。其中,堡垒用来防御而坦克用来攻击,这当然只是两种不同的行为而已。这里假设坦克是不能越过堡 垒的,同时假设堡垒过于强大,以至于坦克无法利用炮轰的方式将其炸毁。那么,我们假设战车各自为派(那么,可以想见,在没有任何的堡垒的情况下,方格上最 多有N辆战车),希望我们在初始阶段尽可能地放置更多的互相不受攻击的战车。这也是许多坦克游戏中的初始布局的基本要求,因为,如果一开始你就可以攻击到 对方,这样有失游戏的公平性。
这是一个坦克游戏的残局,在布局阶段,是不允许双方的坦克受到彼此之间的攻击的。
【分析】从第一行开始随机产生一个位置,看战车在该位置上能否放置,分三种情况讨论:
a. 该随机位置对应在棋盘上是一个堡垒,则不能放置;
b. 该位置与前面放置的战车相冲突,即在受前面放置战车攻击的位置上,则
也不能放置;
c. 该随机位置不受攻击也不是堡垒,则可以放置;
如果不能放置,则重新产生一个随机位置再判断,如果可以放置, 则放在该位置上并记录战车个数和该战车可能攻击的位置。以这种方法从第一行开始放置,第一行不能放置战车后再放第二行,直至第n行结束。
【设计】算法设计:
a. 建立随机数类CRandomNumber;
b. 函数CheckPlace判断是否可以放入战车,同时查看所放战车的攻击位置;
c. 函数MaxChes产生随机位置,放置并记录战车数。
1 /*
2 Highlights:
3 (1)采用随机数做种,这里的参数的选择,以及模拟实验的次数,是权衡正确度和时间效率的准则,需要人为想好
4 (2)判断的时候,设置flag标志位,只有某一行的八个列全都标记了X的情况下才置flag为false,否则只要有一处有可能都不应该放过
5 (3)每一行Check的时候,要将这一个战车的四周的"攻击点"都标记出来,同时,如果碰到了X,则停止标记
6 (4)可以看到,这里即使是随机化方法,其中也包含着很多确定性的执着,这一点很有意思
7 (5)如果模拟K次的话,该问题的时间复杂度为O(K*n^2)
8 */
9
10 #include<iostream.h>
11 #include<fstream>
12 #include<time.h>
13 #include<stdlib.h>
14
15 const unsigned long multiplier=1194211693L;
16 const unsigned long adder=12345L;
17
18 //随机数类
19 class CRandomNum
20 {
21 private:
22 unsigned long nSeed;
23 public:
24 CRandomNum(unsigned long s=0);
25 unsigned short Random(unsigned long n);
26 }
27
28 //用计算机的随机时间做种
29 CRandomNum::CRandomNum(unsigned long s)
30 {
31 if(s==0)
32 nSeed=time(0);
33 else
34 nSeed=s;
35 }
36
37 //对生成的种子做处理,使之符合具体问题的规模,这里采用(乘+加+取模)的形式
38 unsigned short CRandomNum::Random(unsigned long n)
39 {
40 nSeed=multiplier*nSeed+adder;
41 return (unsigned short)((nSeed>>16)%n);
42 }
43
44 bool CheckPlaceChe(int **b,char **a,int x,int y,int n)
45 {
46 if(b[x][y]==1) return false;
47 else
48 {
49 //这里的置1不代表堡垒,而代表放置一个战车,这里,我们还要考虑放置之后对周围的格子的影响
50 //这里,我们将战车四周的位置都标记为"受攻击点",直到有"X"标记的障碍物阻挡之后不再计数
51 b[x][y]=1;
52 if((y>=1)&&(y<=n))
53 {
54 for(int i=y-1;i>=0;i--)
55 {
56 if((b[x][i]==1)&&(a[x][i]=='X'))
57 break;
58 else
59 b[x][i]=1;
60 }
61 }
62 if((y<=n-1)&&(y>=0))
63 {
64 for(int i=y+1;i<n;i++)
65 {
66 if((b[x][i]==1)&&(a[x][i]=='X'))
67 break;
68 else
69 b[x][i]=1;
70 }
71 }
72 if((x>=1)&&(x<=n))
73 {
74 for(int j=x-1;j>=0;j--)
75 {
76 if((b[j][y]==1)&&(a[j][y]=='X'))
77 break;
78 else b[j][y]=1;
79 }
80 }
81 if((x>=0)&&(x<=n-1))
82 {
83 for(int j=x-1;j<n;j++)
84 {
85 if((b[j][y]==1)&&(a[j][y]=='X'))
86 break;
87 else b[j][y]=1;
88 }
89 }
90 return true;
91 }
92 }
93
94 int MaxChes(int **b,char **a,int *x,int n)
95 {
96 int max1=0;
97 //设置CRandomNum类中的静态实例
98 static CRandomNum rnd;
99 for(int i=0;i<n;i++)
100 {
101 bool flag=false;
102 do
103 {
104 for(int j=0;j<n;j++)
105 {
106 if(b[i][j]==0)
107 {
108 flag=true;
109 break;
110 }
111 //如果这个位置上面放置堡垒的话,是不能放置的,按照规则1
112 else
113 {
114 flag=false;
115 continue;
116 }
117 }
118 //如果"可能放置"的话,那么先生成一个在[0,n]之间的随机数,再利用CheckPlace函数检查一下这个位置是否可以放置
119 if(flag)
120 {
121 x[i]=rnd.Random(n);
122 if(CheckPlace(b,a,i,x[i],n))
123 max1++;
124 }
125 }
126 //利用do--while可以在执行命令之后再判断条件,符合这里的逻辑关系,当所有的随机情况都处理完后再退出
127 while(flag);
128 }
129 return max1;
130 }
131
132 int main()
133 {
134 int i,j,n;
135 cin>>n;
136 //开启两个二维数组,一个放数,一个放字符,b数组为a数组的"翻译数组",意思是如果a数组的某项存在碉堡,那么b数组在这个位置上需要置1
137 char **a=new char*[n];
138 for(i=0;i<n;i++)
139 a[i]=new char[n];
140 for(i=0;i<n;i++)
141 for(j=0;j<n;j++)
142 cin>>a[i][j];
143 int **b=new int*[n];
144 for(i=0;i<n;i++)
145 b[i]=new int[n];
146 int max=0;
147 //由于之后是对行进行扫描,这里开一个一维数组的目的也在于此
148 int *x=new int[n];
149 for(i=0;i<n;i++)
150 x[i]=0;
151 int t=0;
152 //实验15000次(具体的次数的选取需要参考设置的随机数生成器的参数)
153 while(t<15000)
154 {
155 for(int i=0;i<n;i++)
156 {
157 for(int j=0;j<n;j++)
158 {
159 if(a[i][j]=='X')
160 b[i][j]=1;
161 else
162 b[i][j]=0;
163 }
164 }
165 //每一次,得到一个"可能的最大值"
166 int max2=MaxChes(b,a,x,n);
167 if(max<max2) max=max2;
168 t++;
169 }
170 cout<<max;
171 //主动地丢弃数组内存,这是一个好的习惯,垃圾清理机制可是C++中没有的哦!
172 delete []x;
173 for(i=0;i<n;i++)
174 delete[] b[i];
175 delete[] b;
176 return 0;
177 }
178
179
2 Highlights:
3 (1)采用随机数做种,这里的参数的选择,以及模拟实验的次数,是权衡正确度和时间效率的准则,需要人为想好
4 (2)判断的时候,设置flag标志位,只有某一行的八个列全都标记了X的情况下才置flag为false,否则只要有一处有可能都不应该放过
5 (3)每一行Check的时候,要将这一个战车的四周的"攻击点"都标记出来,同时,如果碰到了X,则停止标记
6 (4)可以看到,这里即使是随机化方法,其中也包含着很多确定性的执着,这一点很有意思
7 (5)如果模拟K次的话,该问题的时间复杂度为O(K*n^2)
8 */
9
10 #include<iostream.h>
11 #include<fstream>
12 #include<time.h>
13 #include<stdlib.h>
14
15 const unsigned long multiplier=1194211693L;
16 const unsigned long adder=12345L;
17
18 //随机数类
19 class CRandomNum
20 {
21 private:
22 unsigned long nSeed;
23 public:
24 CRandomNum(unsigned long s=0);
25 unsigned short Random(unsigned long n);
26 }
27
28 //用计算机的随机时间做种
29 CRandomNum::CRandomNum(unsigned long s)
30 {
31 if(s==0)
32 nSeed=time(0);
33 else
34 nSeed=s;
35 }
36
37 //对生成的种子做处理,使之符合具体问题的规模,这里采用(乘+加+取模)的形式
38 unsigned short CRandomNum::Random(unsigned long n)
39 {
40 nSeed=multiplier*nSeed+adder;
41 return (unsigned short)((nSeed>>16)%n);
42 }
43
44 bool CheckPlaceChe(int **b,char **a,int x,int y,int n)
45 {
46 if(b[x][y]==1) return false;
47 else
48 {
49 //这里的置1不代表堡垒,而代表放置一个战车,这里,我们还要考虑放置之后对周围的格子的影响
50 //这里,我们将战车四周的位置都标记为"受攻击点",直到有"X"标记的障碍物阻挡之后不再计数
51 b[x][y]=1;
52 if((y>=1)&&(y<=n))
53 {
54 for(int i=y-1;i>=0;i--)
55 {
56 if((b[x][i]==1)&&(a[x][i]=='X'))
57 break;
58 else
59 b[x][i]=1;
60 }
61 }
62 if((y<=n-1)&&(y>=0))
63 {
64 for(int i=y+1;i<n;i++)
65 {
66 if((b[x][i]==1)&&(a[x][i]=='X'))
67 break;
68 else
69 b[x][i]=1;
70 }
71 }
72 if((x>=1)&&(x<=n))
73 {
74 for(int j=x-1;j>=0;j--)
75 {
76 if((b[j][y]==1)&&(a[j][y]=='X'))
77 break;
78 else b[j][y]=1;
79 }
80 }
81 if((x>=0)&&(x<=n-1))
82 {
83 for(int j=x-1;j<n;j++)
84 {
85 if((b[j][y]==1)&&(a[j][y]=='X'))
86 break;
87 else b[j][y]=1;
88 }
89 }
90 return true;
91 }
92 }
93
94 int MaxChes(int **b,char **a,int *x,int n)
95 {
96 int max1=0;
97 //设置CRandomNum类中的静态实例
98 static CRandomNum rnd;
99 for(int i=0;i<n;i++)
100 {
101 bool flag=false;
102 do
103 {
104 for(int j=0;j<n;j++)
105 {
106 if(b[i][j]==0)
107 {
108 flag=true;
109 break;
110 }
111 //如果这个位置上面放置堡垒的话,是不能放置的,按照规则1
112 else
113 {
114 flag=false;
115 continue;
116 }
117 }
118 //如果"可能放置"的话,那么先生成一个在[0,n]之间的随机数,再利用CheckPlace函数检查一下这个位置是否可以放置
119 if(flag)
120 {
121 x[i]=rnd.Random(n);
122 if(CheckPlace(b,a,i,x[i],n))
123 max1++;
124 }
125 }
126 //利用do--while可以在执行命令之后再判断条件,符合这里的逻辑关系,当所有的随机情况都处理完后再退出
127 while(flag);
128 }
129 return max1;
130 }
131
132 int main()
133 {
134 int i,j,n;
135 cin>>n;
136 //开启两个二维数组,一个放数,一个放字符,b数组为a数组的"翻译数组",意思是如果a数组的某项存在碉堡,那么b数组在这个位置上需要置1
137 char **a=new char*[n];
138 for(i=0;i<n;i++)
139 a[i]=new char[n];
140 for(i=0;i<n;i++)
141 for(j=0;j<n;j++)
142 cin>>a[i][j];
143 int **b=new int*[n];
144 for(i=0;i<n;i++)
145 b[i]=new int[n];
146 int max=0;
147 //由于之后是对行进行扫描,这里开一个一维数组的目的也在于此
148 int *x=new int[n];
149 for(i=0;i<n;i++)
150 x[i]=0;
151 int t=0;
152 //实验15000次(具体的次数的选取需要参考设置的随机数生成器的参数)
153 while(t<15000)
154 {
155 for(int i=0;i<n;i++)
156 {
157 for(int j=0;j<n;j++)
158 {
159 if(a[i][j]=='X')
160 b[i][j]=1;
161 else
162 b[i][j]=0;
163 }
164 }
165 //每一次,得到一个"可能的最大值"
166 int max2=MaxChes(b,a,x,n);
167 if(max<max2) max=max2;
168 t++;
169 }
170 cout<<max;
171 //主动地丢弃数组内存,这是一个好的习惯,垃圾清理机制可是C++中没有的哦!
172 delete []x;
173 for(i=0;i<n;i++)
174 delete[] b[i];
175 delete[] b;
176 return 0;
177 }
178
179