1. 主页
  2. 文档
  3. Level2题解(1-10)
  4. 附.模拟法

附.模拟法

在信息学奥赛中,有一类问题是模拟一个游戏的对弈过程,或者模拟一项任务的操作过程,进行统计计分、判断输赢等。这些问题很难建立数学模型用特定算法解决,一般只能采用“模拟”法。用模拟法解题必须关注以下几个问题:审题要仔细,游戏规则不能错;分析要全面,各种特例不能漏;编程要细心,测试运行要到位。

  • 定义:用计算机的操作模拟现实世界中的事务变化,从而完成相应任务。
  • 做法: 读懂题目,用纸和笔,针对样例数据,模拟计算一遍,理清各个数据之间的内在逻辑,然后建立 数学模型, 用计算机模拟出来数据的处理过程。
  • 可以用于解决第1、2道题目

读题-> 建模-> 代码实现-> 调试、优化

技巧:
通过程序设计,用计算机来模拟生成日常生活中的相关数据,
以分析相应的数据,从而对实际工作进行预判。

算法思想:

​ 在程序设计语言中,可以使用随机函数来模拟自然界中发生的不可预测的情况。在C语言中使用srand()和rand()两个函数来生成随机数。

提示:函数srand()用来初始化随机数发生器,然后使用rand()函数来生成随机数

要使用这两个函数,要使用的头文件是“time.h”和“stdlib.h”。 一般情况下使用过程为:

srand(time(NULL));
i=rand()%100+1;

使用上面的语句,变量i的值将会在1~100之间。

实例:用模拟算法来编写一个猜数字游戏。由计算机随机生成一个1~100的整数,然后由用户来猜这个数,根据用户猜测的次数分别给出不同的提示文字。

#include <time.h>
#include <iostream>
#include <cstdio>
#include <conio.h>
#include <stdlib.h> 
using namespace std;
int main()
{
	int n,m,i=0;
	srand(time(NULL));
	n=rand()%100+1;
	do{
		cout<<"输入猜测的数";
		cin>>m;
		i++;
		if(m>n)
		cout<<"NO!数太大了" <<endl;
		else if(m<n) 
		{
			cout<<"NO!数太小了"<<endl; 
		}
	}while (m!=n);
	cout<<"yes"<<endl;
	cout<<"一共猜了"<<i<<"次" ;
	getch();
	return 0; 
}

自然界和日常生活中的有些事物,若用计算机很难建立起枚举、贪心等算法,甚至没法建立数学模型。我们解决这类问题可用模拟法。所谓模拟法,就是用计算机模拟某个过程,通过改变数学模型的各种参数,进而观察变更这些参数所引起的过程状态的变化,然后从中得出解答。

实例讲解:

P1042 乒乓球

  • 重点: 如果一局比赛刚开始,则此时比分为0比0。直到分差大于或者等于2,才一局结束
  • 难点:
    • 读取若干行, 不是一行, 不是固定行数, 怎么判断读取完毕呢?
    • ‘E’ 代表结束, 并且’E’后面可能还有输入
    • 超过11 或者 21分, 分值要差2分才行.
    • 下一局开始 0:0 也要输出.

参考代码:

#include <bits/stdc++.h>
using namespace std;

int a11[10000], b11[10000];
int a21[10000], b21[10000];
int cnt11, cnt21;
char c;

int main()
{
while(c = getchar()) {
if(c == 'E')
break;

if(c=='W') {
a11[cnt11]++;
a21[cnt21]++;
} else if(c == 'L') {
b11[cnt11]++;
b21[cnt21]++;
} else
continue;

if(a11[cnt11] >= 11 || b11[cnt11] >= 11) {
if(abs(a11[cnt11] - b11[cnt11]) >= 2)
cnt11++;
}

if(a21[cnt21] >= 21 || b21[cnt21] >= 21) {
if(abs(a21[cnt21] - b21[cnt21]) >= 2)
cnt21++;
}
}

int i;
for(i = 0; i <= cnt11; i++) {
printf("%d:%d\n", a11[i], b11[i]);
}

printf("\n");

for(i = 0; i <= cnt21; i++) {
printf("%d:%d\n", a21[i], b21[i]);
}

return 0;
}

P1328 生活大爆炸版石头剪刀布

难点:

  • 如何建模? 不可能把每种出拳都用if来比较的. 上面图形多么像个二维数组啊. 就用二维数组吧, 来描述甲对乙的输赢的分数. 赢为1, 输为0. 0 表示“剪刀”,1 表示“石头”,2 表示“布”,3 表示“蜥蜴人”,4表示“斯波克”.
int score[5][5] = {
{0,0,1,1,0},
{1,0,0,1,0},
{0,1,0,0,1},
{0,0,1,0,1},
{1,1,0,0,0}
};
  • 周期… 周期到了从头开始吧. 可以用取模运算, 也可以用if判断.

参考代码:

#include <bits/stdc++.h>
using namespace std;

int score[5][5] = {
{0,0,1,1,0},
{1,0,0,1,0},
{0,1,0,0,1},
{0,0,1,0,1},
{1,1,0,0,0}
};

int a[208],b[208];
int n, an, bn;
int sa, sb;

int main()
{
int i;
scanf("%d %d %d", &n, &an, &bn);
for(i = 0; i < an; i++)
scanf("%d", &a[i]);
for(i = 0; i < bn; i++)
scanf("%d", &b[i]);

int ai = 0, bi = 0;
for(i = 0; i <n; i++) {
sa += score[a[ai]][b[bi]];
sb += score[b[bi]][a[ai]];
ai =(ai + 1) %an;
bi = (bi + 1) %bn;
}
printf("%d %d", sa, sb);
return 0;
}

P1563 玩具谜题

难点:

  • 描述好多, 题意需要仔细分析
  • 环和取模的关系
  • 数组存放按逆时针和习惯相反

参考代码

#include <bits/stdc++.h>
using namespace std;

int n,m;
char toy[100008][12];
int a[100008];

int main()
{
int i;
int d, s;

scanf("%d %d", &n, &m);
for(i = 0; i < n; i++){
scanf("%d %s", &a[i], toy[i]);
}

int idx = 0;
int flag = 1; //逆时针+ 1; 顺时针 -1 -
for(i=0; i < m; i++)
{
scanf("%d %d", &d, &s);
if (a[idx] == 0) {
if(d == 0){
flag = -1;
} else {
flag = 1;
}
} else {
if(d == 0){
flag = 1;
} else {
flag = -1;
}
}

idx = (idx + flag * s + n)%n;
}

printf("%s", toy[idx]);
return 0;
}

补充知识

函数名格式功能说明列子
绝对值函数abs(x)求一个数的绝对值abs(-5) = 5
自然数指数函数exp(x)求实数x的自然指数e^nenexp(1)=2.718282
向下取整floor(x)求不大于实数x的最大整数floor(3.14) = 3
向上取整ceil(x)求不小于实数x的最小整数ceil(3.14) = 4
自然对数函数log(x)计算x的自然数对数log(1) = 0
指数函数pow(x,y)计算x^yxy 结果为双精度实数pow(2,3) = 8
随机函数rand()产生0 到 RAND-MAX之间的随机数
平方根函数sqrt(x)求实数x的平方根sqrt(25) = 5

题单:

  • P1042 乒乓球
  • P2670 扫雷游戏
  • P1563 玩具谜题
  • P4924 [1007]魔法少女小Scarlet
  • P1328 生活大爆炸版石头剪刀布
  • P1518 [USACO2.4]两只塔姆沃斯牛 The Tamworth Two
  • P1067 多项式输出
  • P1098 字符串的展开