模拟与高精度(第一部分)

新手刷题网站,快速入门!洛谷

模拟与高精度


3月30日

[NOIP2003 普及组] 乒乓球

题目描述

华华通过以下方式进行分析,首先将比赛每个球的胜负列成一张表,然后分别计算在 11 分制和 21 分制下,双方的比赛结果(截至记录末尾)。

比如现在有这么一份记录,(其中 W 表示华华获得一分,L 表示华华对手获得一分):

WWWWWWWWWWWWWWWWWWWWWWLW

在 11 分制下,此时比赛的结果是华华第一局 11 比 0 获胜,第二局 11 比 0 获胜,正在进行第三局,当前比分 1 比 1。而在 21 分制下,此时比赛结果是华华第一局 21 比 0 获胜,正在进行第二局,比分 2 比 1。如果一局比赛刚开始,则此时比分为 0 比 0。直到分差大于或者等于 2,才一局结束。

你的程序就是要对于一系列比赛信息的输入( WL 形式),输出正确的结果。

思路

可以先保存输入的数据,然后进行处理并输出,也可以边输入边处理,保存处理结果,最后统一输出。本题采用边输入边处理的方法。

注意:乒乓球比赛中并非一方得分到达11分时比赛结束,而是达到11分且分数比对手高2分才算比赛结束,即11:10比赛并未结束。

代码

#include<iostream>
using namespace std;
int main(){
    char m;
    int a,b,c,d,i,j,q,p;
    a=b=c=d=i=j=q=p=0;
    int i1[100001];
    int i2[100001];
    while((m=getchar())!='E'){
        // 记录赛况
        if(m=='W'){
            a++;c++;
        }else if(m=='L'){
            b++;d++;
        }
        // 11分制记录单局比赛结构
        if((a>=11&&a-b>1)||(b>=11&&b-a>1)){
        	i1[i++]=a;
            i1[i++]=b;
            a=b=0;
        }
        // 21分制记录单局比赛结果
        if((c>=21&&c-d>1)||(d>=21&&d-c>1)){
            i2[j++]=c;
            i2[j++]=d;
            c=d=0;
        }
    }
    // 输出11分制结果
    while(q<i) cout<<i1[q++]<<':'<<i1[q++]<<endl;
    cout<<a<<':'<<b<<endl;
    cout<<endl;
    // 输出21分制结果
    while(p<j) cout<<i2[p++]<<':'<<i2[p++]<<endl;
    cout<<c<<':'<<d<<endl;
    return 0;
}

[NOIP2015 普及组] 扫雷游戏

题目描述

扫雷游戏是一款十分经典的单机小游戏。在n行m列的雷区中有一些格子含有地雷(称之为地雷格),其他格子不含地雷(称之为非地雷格)。玩家翻开一个非地雷格时,该格将会出现一个数字——提示周围格子中有多少个是地雷格。游戏的目标是在不翻出任何地雷格的条件下,找出所有的非地雷格。

现在给出n行m列的雷区中的地雷分布,要求计算出每个非地雷格周围的地雷格数。

注:一个格子的周围格子包括其上、下、左、右、左上、右上、左下、右下八个方向上与之直接相邻的格子。

思路

二维数组,数组比输入多了两行两列,方便统一代码计算雷数,不用考虑特殊情况。

代码

#include<iostream>
#include<cstring>
using namespace std;

int main(){
	int n,m;
	cin>>n>>m;
	int a[n+2][m+2];
    //数组初始化为零
	memset(a,0,sizeof(a));
	char t;
    //*转成-1,表示雷区
	for(int i=1;i<n+1;i++){
		for(int j=1;j<m+1;j++){
			cin>>t;
			if(t=='*') a[i][j]=-1;
		}	
	}
	for(int i=1;i<n+1;i++){
		for(int j=1;j<m+1;j++){
			if(a[i][j]==-1) cout<<'*';
			else{
                //计算该点周围的雷数
				for(int x=i-1;x<i+2;x++){
					for(int y=j-1;y<j+2;y++){
						if(x==i&&y==j) continue;
						if(a[x][y]==-1) a[i][j]++;
					}
				}
				cout<<a[i][j];
			}
		}	
		cout<<endl;
	}
	return 0;
} 

[NOIP2016 提高组] 玩具谜题

题目描述

略,见题目链接

思路

朝向与方向相同时,做--操作;朝向与方向相反时,做++操作;即00--,11--;01++,10++.

代码

#include<iostream>
using namespace std;
int main(){
	int n,m,x,y,t=0;
	cin>>n>>m;
	int a[n];
	string s[n];
	for(int i=0;i<n;i++) cin>>a[i]>>s[i];
	for(int i=0;i<m;i++){
		cin>>x>>y;
        //同向与反向处理
		if(x==a[t]) t=t>=y?t-y:n+(t-y);
		else t=t+y<=n-1?t+y:t+y-n;
	} 
	cout<<s[t];
	return 0;
}

A+B Problem(高精)

题目描述

高精度加法,相当于a+b problem,不用考虑负数.

输入格式

分两行输入。a,b <=10^500

输出格式

输出只有一行,代表a+b的值

思路

首先,这题我取巧了,用了swap,省略大量判断,其次,大数相加先考虑A+进位+B的情况,当B结束之后,再考虑A+进位,当A也结束后,再考虑进位。ascii码 0 ->48

代码

#include<iostream>
using namespace std;
int main(){
	string a,b;
	cin>>a>>b;
	int x = 0;
	//默认a最长,如果b更长则交换ab,保持a最长
	if(a.size()<b.size()) a.swap(b);
	int p = a.size() - b.size();
    //考虑a+b+进位
	for(i=b.size()-1;i>=0;i--){
		int t=a[i+p]+b[i]+x-96;
		a[i+p] = t%10 + 48;
		x = t/10;
	}
    //考虑a+进位
	if(x){
		for(int i = p-1;i>=0;i--){
			int t =  a[i] + x - 48;
			a[i] = t%10 + 48;
			x = t/10;
			if(!x) break;
		}
	}
    //考虑进位
	if(x) cout<<x;
	cout<<a;
	return 0;
}

4月1日

A*B Problem

题目描述

求两数的积。

思路

大数相乘,思路,不仅要加上进位,还要加上自身。

快读使用有讲究,本次使用快读时一开始写法如下,明明输入格式是一行一个整数,却老报错,改成后面的写法就通过了。

    //前
    while((n=getchar())!='\n') a[la++] = n-48;
    while((n=getchar())!='\n') b[lb++] = n-48;
	//后
	while((n=getchar())>='0'&&n<='9') a[la++] = n-48;
	while(n<'0'||n>'9') n=getchar();
	while(n>='0'&&n<='9') b[lb++] = n-48, n = getchar();

代码

#include<iostream>
using namespace std;
const int N=2010;
int a[N],b[N],c[N*N];
int la,lb,lc;
int main(){
	char n;
	while((n=getchar())>='0'&&n<='9') a[la++] = n-48;
	while(n<'0'||n>'9') n=getchar();
	while(n>='0'&&n<='9') b[lb++] = n-48, n = getchar();
	for(int i=la-1;i>=0;i--){
		int u=0;
		int t=la-1-i;
		for(int j=lb-1;j>=0;j--){
            //加上进位u,再加上上次计算的结果,比如999*999 计算到第二个9时,前一个9计算结果是8991,此时要加上8991中第二个9。
		    int temp = a[i] * b[j] + c[t] + u;
			u = temp/10;
			c[t++] = temp%10;
		}
		if(u) c[t]=u;
		lc = lc>t?lc:t;	
	} 
	while(c[lc]==0&&lc!=0) lc--;
	for(lc;lc>=0;lc--) cout<<c[lc];
	return 0;
}

[NOIP1998 普及组] 阶乘之和

题目描述

用高精度计算出 S=1!+2!+3!+⋯+n!(n≤50)。

其中“!”表示阶乘,例如:5!=5×4×3×2×1。

思路

简单说一下本题,本题可以说是一个缝合怪,缝合了前面两题,大数相乘和大数相加,同时本次题解又小小使用了一下递归。

再说一下做题思路,简单说就是递归的进行大数相乘,并在中间穿插使用大数相加,具体思路见代码注解。

再记录一下本次做题历程,本次做题历程可谓是一波三折,一开始以为是求阶乘,照着阶乘的方向做去了,这也为后面求阶乘之和埋下了两个陷阱。

第一个陷阱,因为调用上次大数相乘的方法,该方法输入是正序,输出倒序,即在输入时,int a[3]={1,2,3}就代表数字123,然而输出时则变成了321,问题是,本次题解用到了递归,即,输出即输入,这是第一个陷阱;

第二个陷阱,如果单纯求阶乘,方向无所谓,比如5的阶乘,无论是5×4×3×2×1还是1×2×3×4×5,结果是一致的,但是涉及到阶乘之和就不一样了,比如5的阶乘之和,1 + 1×2 + 1×2×3 + 1×2×3×4 + 1×2×3×4×5,如果正序,可以每次相乘之后都进行大数相加,即,可以在相乘的阶段同时处理相加,但是如果方向反了,就无法同时处理,因为5 + 5×4 + 5×4×3 + 5×4×3×2 + 5×4×3×2×1并不是阶乘之和!!!

第一个陷阱是之前写大数相乘留下的,第二个是写阶乘留下的。

这就完了?

当然没有,之前写大数相加也留下了陷阱,哭,为了方便输出,当时计算结束如果还有进位,直接输出进位,之后在遍历数组,但是在本题中调用必须把进位放进数组里面,因为本题中的结果也要继续进行大数相加,即,输出即输入,但是输出少了一位问题就严重了,这是第三个陷阱;

第四个陷阱,调用之前写的大数相加,用到了swap函数,即通过数组交换,永远保持把结果赋值给长的数组,但是我们知道,大数相加的两个输入,一个输入是上一个数字的阶乘之和,另一个输入是当前数字的阶乘,按照正确思路,永远把结果赋值给上一个数字的阶乘之和,同时保持当前数字的阶乘不被污染,但是不见得上一个数字的阶乘之和比当前数字的阶乘长呀,比如4的阶乘之和为33,但是5的阶乘可是120呀,之后的赋值可都是照着120去了,问题可就严重了,其一:要知道当前数字的阶乘是不能随便改动的呀,为啥?因为递归进行大数相乘的两个输入分别是当前数字的阶乘(不能被污染)和下一个数字呀!!!其二:我们的上一个数字的阶乘之和就不参与计算,也就没有获得当前数字的阶乘了!

当真是一次顾此失彼,大方向正确,小毛病不断地ac历程呀,愚人节哪有不被骗的,hhhh。

求阶乘

#include<iostream>
#include<cstring>
using namespace std;
//解决第一个陷阱,反转数组
int *reverseInt(int a[],int la){
	for(int i=0;i<(la+1)/2;i++){
		int t = a[i];
		a[i] = a[la-i];
		a[la-i] = t;
	}
	return a;
}
//大数相乘
int *multiply(int a[],int b[],int la,int lb){
	int c[100001];
	int lc=0;
	for(int i=la-1;i>=0;i--){
		int u=0;
		int t=la-1-i;
		for(int j=lb-1;j>=0;j--){
			int temp = a[i] * b[j] + c[t] + u;
			u = temp/10;
			c[t++] = temp%10;
		}
		if(u) c[t]=u;
		lc = lc>t?lc:t;	
	} 
    //解决前导零
	while(c[lc]==0&&lc!=0) lc--;
    
	int* c_r = reverseInt(c,lc);
	
    if(b[0]*10 + b[1]>1){
		if(b[1]==0&&b[0]>0){
			b[0]--;
			b[1]=9;
		}else b[1]--;
		
	}
	//递归执行大数相乘,直到递归到1,第二个陷阱,方向相反
	if(b[0]*10 + b[1]>1){
		c_r = multiply(c_r,b,lc+1,2);	
	}else{
		for(int i=0;i<lc+1;i++){
			cout<<c_r[i];
		}
	}
	return c_r;
}
int main(){
	int x;
	cin>>x;
	if(x<3){
		cout<<x;
	}else{
		int a[2]={x/10,x%10};
		int b[2]={(x-1)/10,(x-1)%10};
		multiply(a,b,2,2);
	}
	return 0;
}

阶乘之和

#include<iostream>
#include<cstring>
using namespace std;
int ans[100001];
int lans = 1;
int num = 0;
//大数相加,两个数组分别代表,上一个数字的阶乘之后,本数字的阶乘
int addInt(int a[],int b[],int la,int lb,int n){
    num++;
    int flag=0;
    //自己给自己设计的swap陷阱,即第四个陷阱
	if(la<lb){
        swap(a,b);
	    swap(la,lb);
	    flag = 1;
	}
	int p = la-lb;
	int x=0;
	for(int i=lb-1;i>=0;i--){
		int t=a[i+p]+b[i]+x;
		a[i+p] = t%10;
		x = t/10;
	}
	if(x){
		for(int i = p-1;i>=0;i--){
			int t =  a[i] + x;
			a[i] = t%10;
			x = t/10;
			if(!x) break;
		}
	}
    //第三个陷阱,即进位问题,进位应放入数组内!!x是进位
	if(x){
	    for(int i=la;i>0;i--) a[i] = a[i-1];
	    a[0] = x;
	    la++;
	};
    //解决陷阱四的第二步,如果我们赋值给了当前数字的阶乘,则我们把值在赋值给上一个数字的阶乘之和
	if(flag==1) for(int i=0;i<la;i++) b[i]=a[i];
	if(num==n-1) for(int i=0;i<la;i++) cout<<a[i];
	return la;
}
//反转数组,解决陷阱一
int *reverseInt(int a[],int la){
	for(int i=0;i<(la+1)/2;i++){
		int t = a[i];
		a[i] = a[la-i];
		a[la-i] = t;
	}
	return a;
}
//大数相乘,输入为当前数字的阶乘和下一个数字
int *multiply(int a[],int b[],int la,int lb,int x){
	int c[100001];
	int lc=0;
	for(int i=la-1;i>=0;i--){
		int u=0;
		int t=la-1-i;
		for(int j=lb-1;j>=0;j--){
			int temp = a[i] * b[j] + c[t] + u;
			u = temp/10;
			c[t++] = temp%10;
		}
		if(u) c[t]=u;
		lc = lc>t?lc:t;	
	} 
	while(c[lc]==0&&lc!=0) lc--;
	int* c_r = reverseInt(c,lc);
    //解决陷阱四的第一步,复制一份当前数字的阶乘,用于大数相加,保证了当前数字阶乘的纯洁,以便用于后续的大数相乘
	int c_clone[100001];
	for(int i=0;i<lc+1;i++) c_clone[i]=c_r[i];
    //大数相加操作,可以看到我们输入的是上一个数字的阶乘之和以及复制版的当前数字的阶乘
	lans = addInt(ans,c_clone,lans,lc+1,x);
    //解决陷阱二,改正方向
	if(b[0]*10 + b[1]<=x){
		if(b[1]==9&&b[0]<x/10){
			b[0]++;
			b[1]=0;
		}else b[1]++;
	}
    //递归进行大数相乘,我们输入的是当前数字的阶乘以及一下数字
	if(b[0]*10 + b[1]<=x) c_r = multiply(c_r,b,lc+1,2,x);	
	return c_r;
}
int main(){
	int x;
	cin>>x;
	ans[0]=1;
	if(x<3){
		if(x==2) cout<<3;
		else cout<<x;
	}else{
		int a[2]={0,1};
		int b[2]={0,2};
		multiply(a,b,2,2,x);
	}
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值