模拟与高精度
1、模拟
模拟就是按照题目要求,一步一步模拟题目要求的操作。模拟题目通常具有码量大,操作多,思路复杂的特点。这会导致出现难以查错的情况。
步骤:
- 首先你要看懂题目的内容,读的时候一定要仔细认真不然很容易遗漏一些信息,然后在草稿纸上写下每一步要做的操作。
- 建模,建立题目中的各种情况的数学模型。
- 编写程序,调试,运行。
实例:
1.P1042 [NOIP2003 普及组] 乒乓球
题目描述:
华华通过以下方式进行分析,首先将比赛每个球的胜负列成一张表,然后分别计算在 11 分制和 21 分制下,双方的比赛结果(截至记录末尾)。
比如现在有这么一份记录,(其中W 表示华华获得一分,L 表示华华对手获得一分):
WWWWWWWWWWWWWWWWWWWWWWLW
在 11 分制下,此时比赛的结果是华华第一局 11 比 0 获胜,第二局 11 比 0 获胜,正在进行第三局,当前比分 1 比 1。而在 21 分制下,此时比赛结果是华华第一局 21 比 0 获胜,正在进行第二局,比分 2 比 1。如果一局比赛刚开始,则此时比分为 0 比 0。直到分差大于或者等于 2,才一局结束。
你的程序就是要对于一系列比赛信息的输入(WL 形式),输出正确的结果。
输入格式
每个输入文件包含若干行字符串,字符串有大写的W 、L 和 E 组成。其中 E 表示比赛信息结束,程序应该忽略 E 之后的所有内容。
输出格式
输出由两部分组成,每部分有若干行,每一行对应一局比赛的比分(按比赛信息输入顺序)。其中第一部分是 11 分制下的结果,第二部分是 21 分制下的结果,两部分之间由一个空行分隔。
输入样例:
WWWWWWWWWWWWWWWWWWWW WWLWE
输出样例:
11:0 11:0 1:1 21:0 2:1
说明/提示
每行至多 25个字母,最多有 2500 行。
思路:
首先开一个字符数组来储存要输入的字符串,因为上面有说明“每行至多 25个字母,最多有 2500 行。”,所以字符串最长是25*2500=62500,所以我们开一个100000的绰绰有余。
l是字符串的长度,m,n分别计数华华的得分和对手得分,然后使用scanf依次输入每一个字符,但是scanf也会将换行读入,所以在后面我们要将这种情况单独讨论。
然后用for循环依次判断每一个字符,遇到W,m++,遇到L,n++,遇到换行,跳过本次循环,如果其中一个人的得分大于等于11或者21并且差值大于等于2的,进行输出两人的比分,然后在循环结束后在进行一次输出比分,这是不足11或者21分并且差值不大于等于2的情况。
#include <stdio.h>
#include<string.h>
int main(void)
{
char a[100000];
int l,i=0,flag=1,m=0,n=0;//m计数华华得分,n计数对手得分
while(flag==1)
{
scanf("%c",&a[i]);
if(a[i]=='E')
flag=0;
i++;
}
l=strlen(a);
for(i=0;i<l;i++)
{
if(a[i]=='W')
m++;
if(a[i]=='L')
n++;
if(a[i]=='\n')//如果遇到换行,结束本次循环
continue;
if((m>=11||n>=11)&&(n-m>=2||m-n>=2))//计算够不够11回合并且还得得分大于等于2
{
printf("%d:%d\n",m,n);
m=0,n=0;
}
}
printf("%d:%d\n",m,n);//不够11回合的输出
printf("\n");
m=0,n=0;//计数完11分制后将m,n清零
for(i=0;i<l;i++)
{
if(a[i]=='W')
m++;
if(a[i]=='L')
n++;
if(a[i]=='\n')
continue;
if((m>=21||n>=21)&&(n-m>=2||m-n>=2))//计算够不够21回合并且还得得分大于等于2
{
printf("%d:%d\n",m,n);
m=0,n=0;
}
}
printf("%d:%d\n",m,n);//不够21回合的输出
return 0;
}
2、P2670 [NOIP2015 普及组] 扫雷游戏
P2670 [NOIP2015 普及组] 扫雷游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这道题首先会想到开一个二维字符数组,因为已经将行和列的值输入进去了,所以字符数组的大小就可以确定下来,a[n][m+1],为什么要m+1呢,加1的话就会在每一行的末尾加上串结束符。(不这样定义数组也可以)然后遍历每一个字符,如果是'?'就判断各个方向上有没有‘*’,如果有则令t++,然后输出t,如果不是'?',直接输出当前位置的字符,每判断完一行就进行一次换行。
#include <stdio.h>
#include<string.h>
int main(void)
{
int n,m,i,j,t=0;
scanf("%d%d",&n,&m);
char a[n][m+1];//m+1这样就可以在每一行的末尾加上串结束符,不加的话就会在把后面的元素输出
for(i=0;i<n;i++)
{
scanf("%s",a[i]);
}
for(i=0;i<n;i++)
{
for(j=0;j<m;j++)
{
if(a[i][j]=='?')
{
if(a[i-1][j]=='*')//上
t++;
if(a[i+1][j]=='*')//下
t++;
if(a[i][j-1]=='*')//左
t++;
if(a[i][j+1]=='*')//右
t++;
if(a[i-1][j-1]=='*')//左上
t++;
if(a[i+1][j-1]=='*')//左下
t++;
if(a[i-1][j+1]=='*')//右上
t++;
if(a[i+1][j+1]=='*')//右下
t++;
printf("%d",t);
t=0;
}
else
printf("%c",a[i][j]);
}
printf("\n");
}
return 0;
}
3、P1328 [NOIP2014 提高组] 生活大爆炸版石头剪刀布
P1328 [NOIP2014 提高组] 生活大爆炸版石头剪刀布 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这道题我将每种情况一一列出来,谁赢了进行加一,然后输出结果。...........................
#include<stdio.h>
int main(void)
{
int N,Na,Nb,t1=0,t2=0,i;//表示共进行 N 次猜拳,小 A 出拳的周期长度,小 B 出拳的周期长度
scanf("%d%d%d",&N,&Na,&Nb);
int a[Na],b[Nb];//0 表示“剪刀”,1 表示“石头”,2 表示“布”,3 表示“蜥蜴人”,4表示“斯波克“
for(i=0;i<Na;i++)
scanf("%d",&a[i]);
for(i=0;i<Nb;i++)
scanf("%d",&b[i]);
for(i=0;i<N;i++)
{
//i%Na与i%Nb是为了使出拳规律进行周期性循环
if(a[i%Na]==b[i%Nb]);//两人出拳相同
if(a[i%Na]==0&&b[i%Nb]==1)t2++;
if(a[i%Na]==1&&b[i%Nb]==0)t1++;
if(a[i%Na]==0&&b[i%Nb]==2)t1++;
if(a[i%Na]==2&&b[i%Nb]==0)t2++;
if(a[i%Na]==0&&b[i%Nb]==3)t1++;
if(a[i%Na]==3&&b[i%Nb]==0)t2++;
if(a[i%Na]==0&&b[i%Nb]==4)t2++;
if(a[i%Na]==4&&b[i%Nb]==0)t1++;
if(a[i%Na]==1&&b[i%Nb]==2)t2++;
if(a[i%Na]==2&&b[i%Nb]==1)t1++;
if(a[i%Na]==1&&b[i%Nb]==3)t1++;
if(a[i%Na]==3&&b[i%Nb]==1)t2++;
if(a[i%Na]==1&&b[i%Nb]==4)t2++;
if(a[i%Na]==4&&b[i%Nb]==1)t1++;
if(a[i%Na]==2&&b[i%Nb]==3)t2++;
if(a[i%Na]==3&&b[i%Nb]==2)t1++;
if(a[i%Na]==2&&b[i%Nb]==4)t1++;
if(a[i%Na]==4&&b[i%Nb]==2)t2++;
if(a[i%Na]==3&&b[i%Nb]==4)t1++;
if(a[i%Na]==4&&b[i%Nb]==3)t2++;
}
printf("%d %d",t1,t2);
return 0;
}
4、P1067 [NOIP2009 普及组] 多项式输出
P1067 [NOIP2009 普及组] 多项式输出 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这道题比较坑,需要考虑的情况比较多,第一项和最后一项比较特殊,需要分开讨论。
1.第一项系数如果是1的话,前面不需要带+号;
2.如果系数是1或者-1,不需要输出系数(除了最后一项);
3.系数为0,不需要输出;
4.第2到第n-1项时系数为1,前面要加上+号;
5.每次都需要判断指数是否为1,如果等于1,不需要输出系数;
6.最后一项不需要输出x。
#include<stdio.h>
#include<string.h>
int main(void)
{
int n,i;
scanf("%d",&n);
int a[n+1];
for(i=0;i<=n;i++)
scanf("%d",&a[i]);
//第1项
if(n>1)//指数大于1
{
if(a[0]==0);//系数等于0不输出
else if(a[0]==1)printf("x^%d",n);//系数等于1不需要输出系数,并且第一项前面不需要+号
else if(a[0]==-1)printf("-x^%d",n);//需要加上-号
else if(a[0]>1||a[0]<-1)printf("%dx^%d",a[0],n);
}
else if(n==1)//指数等于1
{
if(a[0]==0);
else if(a[0]==1)printf("x");//指数,系数都等于1,都不需要输出
else if(a[0]==-1)printf("-x");
else if(a[0]>1||a[0]<-1)printf("%dx",a[0]);
}
//第2到第n-1项
for(i=1;i<n;i++)
{
if(n-i>1)
{
if(a[i]==0)continue;
else if(a[i]==1)printf("+x^%d",n-i);//第2到第n-1项时系数为1,前面要加上+号
else if(a[i]==-1)printf("-x^%d",n-i);
else if(a[i]>1)printf("+%dx^%d",a[i],n-i);
else if(a[i]<-1)printf("%dx^%d",a[i],n-i);
}
if(n-i==1)
{
if(a[i]==0)continue;
else if(a[i]==1)printf("+x");
else if(a[i]==-1)printf("-x");
else if(a[i]>1)printf("+%dx",a[i]);
else if(a[i]<-1)printf("%dx",a[i]);
}
}
//第n项
if(a[n]>0)printf("+%d",a[n]);//最后一项不需要输出x
else if(a[n]==0);
else if(a[n]<0)printf("%d",a[n]);
return 0;
}
5.P1563 [NOIP2016 提高组] 玩具谜题
P1563 [NOIP2016 提高组] 玩具谜题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这道题我们拿第一个样例来说,一共7个玩具,围成一圈,从第一个玩具开始数,如果第一个玩具朝内(0),并且是从左数(0),那么我们就顺时针开始数,如果是从右数(1),我们就逆时针开始数;如果第一个玩具朝外(1),并且是从左数(0),那么我们就逆时针开始数,如果是从右数(1),我们就顺时针数。
逆时针我们就直接t+=s[i],顺时针就t=t+n-s[i],但是这样就会出现一个问题,数着数着就会超出原本的人数,所以我们每一次数完就取余n。
这样我们就得到了0 0或者1 1就顺时针数,0 1或者1 0就逆时针数,但是我不能将他们合起来写,分开写就可以AC,但是合起来就会有错误,还请哪位大佬帮我解答。
#include <stdio.h>
#include<string.h>
int dir[100000];//0朝内1朝外
char name[100000][20];
int a[100000];//储存0/1表示向左或者向右数(m条指令)
int s[100000];//向左或者向右数几个人
int main(void)
{
int i,j,t=0;//t是小人的下标
int n,m;//表示玩具小人的个数n和指令的条数m
scanf("%d%d",&n,&m);
for(i=0;i<n;i++)
scanf("%d%s",&dir[i],name[i]);
for(i=0;i<m;i++)
scanf("%d%d",&a[i],&s[i]);
for(i=0;i<m;i++)
{
// if((a[i]==0&&dir[t]==0)||(a[i]==1&&dir[t]==1))//顺时针
if(a[i]==0&&dir[t]==0)
t=(t+n-s[i])%n;//取余为了防止越界
else if(a[i]==1&&dir[t]==1)
t=(t+n-s[i])%n;
//if((a[i]==1&&dir[t]==0)||(a[i]==0&&dir[t]==1))//逆时针
else if(a[i]==1&&dir[t]==0)
t=(t+s[i])%n;
else if(a[i]==0&&dir[t]==1)
t=(t+s[i])%n;
}
printf("%s",name[t]);
return 0;
}
2、高精度
高精度算法(High Accuracy Algorithm)是处理大数字的数学计算方法。在一般的科学计算中,会经常算到小数点后几百位或者更多,当然也可能是几千亿几百亿的大数字。一般这类数字我们统称为高精度数,高精度算法是用计算机对于超大数据的一种模拟加,减,乘,除,乘方,阶乘,开方等运算。对于非常庞大的数字无法在计算机中正常存储,于是,将这个数字拆开,拆成一位一位的,或者是四位四位的存储到一个数组中, 用一个数组去表示一个数字,这样这个数字就被称为是高精度数。高精度算法就是能处理高精度数各种运算的算法,但又因其特殊性,故从普通数的算法中分离,自成一家。(百度百科)
int数量级为10的9次方,long long数量级为10的18次方。高精度就是用来计算一些数值比较大的数字,用int,long long不能解决的问题。
实例:
1.P1601 A+B Problem(高精)
P1601 A+B Problem(高精) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
高精度加法,高精度的入门题,做高精度题肯定需要用到数组,所以我定义了5个数组,2个字符数组,3个整形数组。我们一开始先用字符数组将输入的两串数字储存起来,求出他们的长度,然后将他们倒序储存在整形数组里面,将字符转换为数字只需要减个'0',为什么要倒序储存呢?便于后面两串数字相加,高精度加法就是相当于竖式加法,从最低位开始相加,然后逢十进一,将最后结果储存在sum数组中,最后倒序输出。最后结果的长度我们就取输入进去的两串字符串最长的那一个加一即可(防止最高位相加进一)。
算法核心: sum[i]+=s1[i]+s2[i];//加等是为了防止前一位进位
sum[i+1]=sum[i]/10;//进位
sum[i]=sum[i]%10;
加完之后就要消去前导零了,如果最高位没有进位,和的最高位就为零,所以我们要将和的长度减一,l3-1>0为了避免出现0+0的情况,最后我们在倒序输出。
#include<stdio.h>
#include<string.h>
int s1[505],s2[505],sum[505];//要定义在全局,不然就会爆
char a[505],b[505];
int main(void)
{
int l1,l2,l3,i;
scanf("%s",a);
scanf("%s",b);
l1=strlen(a),l2=strlen(b);
for(i=0;i<l1;i++)
s1[i]=a[l1-i-1]-'0';
for(i=0;i<l2;i++)
s2[i]=b[l2-i-1]-'0';
l3=(l1>l2)?l1+1:l2+1;
for(i=0;i<l3;i++)
{
sum[i]+=s1[i]+s2[i];
sum[i+1]=sum[i]/10;
sum[i]=sum[i]%10;
}
if(sum[l3-1]==0&&(l3-1)>0)//删除前导0,l3-1>0为了避免出现0+0的情况
l3--;
for(i=l3-1;i>=0;i--)
printf("%d",sum[i]);
return 0;
}
2.P1303 A*B Problem
P1303 A*B Problem - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
乘法和加法差不多,就是在核心算法部分,还有消前导零部分有些不同。
核心算法: sum[i+j]+=s1[i]*s2[j];
sum[i+j+1]+=sum[i+j]/10;//进位也需要加等,乘数和被乘数下标相反的情况
sum[i+j]%=10;
积的下标和就是两个数下标的和,所以我们直接用sum[i+j]来储存两个数的积。消前导零需要循环,因为积的长度是l=l1+l2,前面可能有不止一个零。
#include<stdio.h>
#include<string.h>
char a[2005],b[2005];
int s1[2005],s2[2005],sum[4000];
int main(void)
{
int i,j,l1,l2,l;
scanf("%s%s",a,b);
l1=strlen(a),l2=strlen(b);
for(i=0;i<l1;i++)
s1[i]=a[l1-i-1]-'0';
for(i=0;i<l2;i++)
s2[i]=b[l2-i-1]-'0';
l=l1+l2;
for(i=0;i<l1;i++)
{
for(j=0;j<l2;j++)
{
sum[i+j]+=s1[i]*s2[j];
sum[i+j+1]+=sum[i+j]/10;//进位
sum[i+j]%=10;
}
}
while(sum[l-1]==0&&l>1)
l--;
for(i=l-1;i>=0;i--)
printf("%d",sum[i]);
return 0;
}
3.P1009 [NOIP1998 普及组] 阶乘之和
P1009 [NOIP1998 普及组] 阶乘之和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
高精度阶乘之和,需要求出每个数的阶乘然后相加,所以我们要准备两个数组,一个储存阶乘,一个储存和,一开始要先判断输入的n是否为1,如果是的话,直接输出1,然后return 0;如果不是的话,我们要求出n和n之前每个数的阶乘。
阶乘: m=a[j]*i+c;
a[j]=m%10;
c=m/10;
和: sum[k]=sum[k]+a[k];
if(sum[k]>=10)
{
sum[k+1]+=1;
sum[k]=sum[k]%10;
}
每次加完之后判断是否需要进位,最后消前导零。
#include<stdio.h>
int a[100],sum[100];//a储存阶乘sum储存阶乘之和
int main(void)
{
int n,i,j,k;
int m;//每个位上的数
int c=0;//进位值,一开始初始化为0
scanf("%d",&n);
a[0]=1,sum[0]=1;//1的阶乘为1,1的阶乘之和为1
if(n==1)
{
printf("1");
return 0;
}
for(i=2;i<=n;i++)
{
for(j=0;j<100;j++)//求阶乘
{
m=a[j]*i+c;
a[j]=m%10;//超过10就要取余,使每一位都小于10
c=m/10;//进位值
}
for(k=0;k<100;k++)//求阶乘之和
{
sum[k]=sum[k]+a[k];
if(sum[k]>=10)
{
sum[k+1]+=1;//加法,最多近1
sum[k]=sum[k]%10;
}
}
}
i=99;
while(sum[i]==0)
i--;
for(j=i;j>=0;j--)
printf("%d",sum[j]);
return 0;
}
4.P1591 阶乘数码
P1591 阶乘数码 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这道题是要求出n的阶乘,然后求这个数中有几个a。每循环一次都要将上一次求出的阶乘储存的那个数组清零。
#include<stdio.h>
#include<string.h>
int s[10000];
int main(void)
{
int t,n[10],a[10],i,j,k;
int m;//每个位上的数
int c=0;//进位值i,一开始初始化为0
int sum;//a出现的次数
scanf("%d",&t);
for(i=0;i<t;i++)
scanf("%d%d",&n[i],&a[i]);
for(k=0;k<t;k++)
{
sum=0;
memset(s,0,sizeof(s));//将数组s所有空间都初始化为0
s[0]=1;//1的阶乘
for(i=2;i<=n[k];i++)//计算阶乘
{
for(j=0;j<10000;j++)
{
m=s[j]*i+c;
s[j]=m%10;//超过10就要取余,使每一位都小于10
c=m/10;//进位值
}
}
i=9999;
while(s[i]==0)
i--;
for(j=i;j>=0;j--)
{
if(s[j]==a[k])
sum++;
}
printf("%d\n",sum);
}
return 0;
}