上海市计算机学会竞赛平台2020年7月月赛题解

第一题盈亏问题

题目背景

成书于汉代的《九章算术》是我国古代的一本数学专著。在其中一个章节里,讨论了一个盈亏问题:
今有(人)买(物),
(每)人出八(钱)盈余三(钱),
(每)人出七(钱)不足四(钱),
问人数、物价各几何?
大意是说,一群人组团买一件物品,若每人出 8 8 8 元,则比物价多了 3 3 3 元;若每人出 7 7 7 元,则比物价少了 4 4 4 元,求物价及参与的人数。

题目描述

假设有一群人买一件物品,如果每个人出 a a a 元,所付的总金额比物品的价格多了 x x x 元;如果每个人出 a − 1 a−1 a1 元,所付的总金额比物品的价格少了 y y y 元。给定 a , x , y a,x,y a,x,y,求人数及物品的价格。

输入格式

三个整数: a , x a,x a,x y y y

输出格式

两个整数:第一个整数表示参与的人数,第二个整数表示物品的价格,中间用一个空格分开。

数据范围

1 ≤ a ≤ 1000 1≤a≤1000 1a1000
1 ≤ x ≤ 1000 1≤x≤1000 1x1000
1 ≤ y ≤ 1000 1≤y≤1000 1y1000

样例数据

输入:

8 3 4

输出:

7 53

输入:

5 2 6

输出:

8 38

分析:

这题我们只需列一个方程就行了(用样例8,3,4解的):

请添加图片描述

或者通过方程分析一下,人数就是 x + y x+y x+y

代码:100分

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

long long x,y,z;

int main(){
	cin>>x>>y>>z;
	cout<< z+y << " ";
	cout<< (z+y)*x-y << endl;
    return 0;
}

第二题 感应门

题目描述

感应门会在有人经过的时候自动打开,冷却 d d d 秒后自动关闭。如果有人在感应门打开的状态下通过,那么冷却时间会重置,重新冷却 d d d 秒后再关闭。
在一段时间内,有 n n n 个人陆续通过了感应门,他们通过感应门的时间点分别是 t 1 , t 2 , ⋯ , t n t_1 ,t_2 ,⋯,t _n t1,t2,,tn ,请计算感应门一共开放了多少时间。

输入格式

第一行:两个整数 n n n d d d n n n 表示通过感应门的人数, d d d表示感应门的冷却时间。
第二行: n n n 个整数 t 1 , t 2 , ⋯ , t n t_1 ,t_2 ,⋯,t_n t1,t2,,tn ,每个数字表达一个人通过感应门的时间点。

输出格式

单个整数,表示感应门总共开启了多少时间。

数据范围

对于 50 % 50\% 50% 的数据, 1 ≤ n ≤ 1000 1≤n≤1000 1n1000
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 100 , 000 1≤n≤100,000 1n100,000
1 ≤ t 1 ≤ t 2 ≤ t 3 ≤ ⋯ ≤ t n ≤ 1 , 000 , 000 , 000 1≤t_1≤t_2≤t_3≤⋯≤t_n≤1,000,000,000 1t1t2t3tn1,000,000,000
1 ≤ d ≤ 1 , 000 , 000 , 000 1≤d≤1,000,000,000 1d1,000,000,000

样例数据

输入:

7 3
1 2 7 10 15 17 22

输出:

18

分析:我们可以看当前的人经过时间和前一个人的经过时间的差值如果大于等于 d d d那么门就冷却 d d d秒关闭了。那如果小于 d d d,那就是门还没关就被重置了,所以是两人之间的差。

管你听没听懂,看代码就完了!!!!!!

代码:100分

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

long long n,d,ans=0,a[1000010];

int main(){
    cin>>n>>d;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<=n;i++){
        if(i==1||a[i]-a[i-1]>=d){//这里的i==1必须加,不然错
            ans+=d;//两人差距大于d 
        }
        else{
            ans+=a[i]-a[i-1];//两人差距小于d 
        }
    }
    cout<< ans << endl;
    return 0;
}

第三题 数根

题目描述

给定一个正整数 n n n,若 n n n 在十进制下的各位数字之和小于 10 10 10,则这个和是 n n n 的数根。否则,继续求这个和在十进制下的各位数字之和,直到结果小于 10 10 10为止,定义最后的结果为 n n n 的数根。
例如, 999 999 999 的数根为 99 99 99,因为 9 + 9 + 9 = 27 9+9+9=27 9+9+9=27,继续分解得 2 + 7 = 9 2+7=9 2+7=9。给定 n n n,请输出它的数根。注意,有一部分 n n n 非常大。

输入格式

单个整数,表示数字 n n n

输出格式

单个整数:表示 n n n 的数根。

数据范围

对于 40 % 40\% 40% 的数据, 1 ≤ n < 2 31 1\leq n< 2^{31} 1n<231
对于 80 % 80\% 80% 的数据, 1 ≤ n < 2 63 1\leq n< 2^{63} 1n<263
对于 100 % 100\% 100% 的数据, 1 ≤ n < 1 0 1000 1\leq n< 10^{1000} 1n<101000

样例数据

输入:

99999999999

输出:

9

说明:

99=>18=>9

输入:

314159265358979323846264338328

输出:

7

分析1:真男人直接拆!!!

本题两个while循环一个判断有没有小于十,另一个负责拆分。

代码1:80分

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

long long n,ans=0;

int main(){
	cin>>n;
	while(n>=10){//负责判断
		ans=0;
		while(n>0){//进行拆分
			ans+=n%10;
			n/=10;
		}
		n=ans;
	}
	cout<< ans << endl;
    return 0;
}

分析2:换成字符串读入

剩下20分你一看:哇,太大了,long long读都读不进去,那换成字符串读在转成整型就行了。

代码2:100分

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

int main(){
	string s;//换字符串
	long long ans=0,cnt=0;
	cin>>s;
	for(int i=0;i<s.size();++i){
		cnt+=s[i]-'0';//进行转化,由于最大数据转化最大才9000,无需再转
	}
	while(cnt>=10){//和上面一样
		ans=0;
		while(cnt>0){
			ans+=cnt%10;
			cnt/=10;
		}
		cnt=ans;
	}
	cout<< cnt << endl;
	return 0;
}

分析3:逐字符读入

字符串都能用了,那它的亲兄弟字符肯定也行呀,不过换成while(cin>>)读入,同样的手法,换汤不换药

代码3:100分

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

int main(){
	char s;//换字符
	long long ans=0,cnt=0;
	while(cin>>s){//结束记得按Ctrl+z
		cnt+=s-'0';//还是转化 
	}
	while(cnt>=10){//和上面一样
		ans=0;
		while(cnt>0){
			ans+=cnt%10;
			cnt/=10;
		}
		cnt=ans;
	}
	cout<< cnt << endl;
	return 0;
}

第四题 倍数区间

题目描述

给定一个数列 a 1 , … , a n a_1,\dots,a_n a1,,an,若有一个区间 [ l , r ] [l,r] [l,r] 满足
a l + a l + 1 + ⋯ + a r − 1 + a r a_l+a_{l+1}+\cdots+a_{r-1}+a_r al+al+1++ar1+ar
k k k 的倍数,则称 [ l , r ] [l,r] [l,r] k k k的倍数区间 。
给定 k k k,请统计在给定的数列中,有多少个区间是 k k k 的倍数区间。

输入格式

第一行:两个整数 n n n k k k
第二行: n n n 个整数 a 1 , … , a n a_1,\dots,a_n a1,,an

输出格式

单个整数:表示倍数区间的数量。

数据范围

对于 30 % 30\% 30% 的数据, n ≤ 200 n\leq 200 n200
对于 60 % 60\% 60% 的数据, n ≤ 5000 n\leq 5000 n5000
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 200000 1\leq n\leq 200000 1n200000

1 ≤ k ≤ 100000 , 0 ≤ a i ≤ 10000 1 \leq k \leq 100000,0 \leq a_i \leq 10000 1k1000000ai10000

样例数据

输入:

4 20
30 20 40 10

输出:

4

说明:

满足要求的区间有:[20],[40],[20,40],[30,20,40,10]

分析1:直接爆推!

这没啥技术难度,只要枚举每个区间,看和是不是 k k k的倍数,就行了,是 O ( n 3 ) O(n^3) O(n3)

代码1:40分

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

long long n,k,a[20000010],cnt=0;

int main(){
	cin>>n>>k;
	for(int i=1;i<=n;++i){
		cin>>a[i];
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n-i+1;++j){
			long long ans=0;
			for(int h=j;h<=j+i-1;++h){
				ans+=a[h];
			}
			if(ans%k==0){
				cnt++;
			}
		}
	}
	cout<< cnt << endl;
    return 0;
}

分析2:干掉循环!!

我们先画一张图:

请添加图片描述

我们只需在当前节点一边往后加一边比较,就行了

代码2:60分

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

long long n,k,ans=0,a[5010];

int main(){
	cin>>n>>k;
	for(int i=1;i<=n;++i){
		cin>>a[i];
	}
	for(int i=1;i<=n;++i){
		long long cnt=0;
		for(int j=i;j<=n;++j){
			cnt+=a[j];
			if(cnt%k==0){
				ans++;
			}
		}
	}
	cout<< ans << endl;
	return 0;
}

分析3:前缀和与同余定理强强联手

我们先用前缀和把数据换一下:

请添加图片描述

再都用 k k k取模:

请添加图片描述

再看看当前数的余数的前面有几个的余数与之配对:

请添加图片描述

再相加:

请添加图片描述

不和结果一毛一样!

代码3:100分

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

const long long MAX=2e5+10;

long long n,k,a[MAX],s[MAX],ans=0,x,y,vh[MAX];

int main(){
    cin>>n>>k;
    for (int i=1;i<=n;i++){
    	cin>>a[i];
		s[i]=s[i-1]+a[i];//前缀和 
	}
    vh[0]=1;//要标一下零 
    for(int i=1;i<=n;i++){
        ans+=vh[s[i]%k];//看前面有没有和它同余的数 
        vh[s[i]%k]++;//把当前余数记进去 
    }
    cout<< ans << endl;
    return 0;
}

第五题 闯关升级

题目描述

小爱可以玩两个游戏,每个游戏各有 n n n 关,每过一关升一级,每关的通关时间是不同的。给定一个整数 t t t,表示小爱玩游戏的时间,请问她应该如何分配时间,才能让升级的次数达到最大?

输入格式

第一行:两个整数 n n n t t t
第二行:nn 个整数 a 1 , a 2 , … , a n a_1,a_2,\dots,a_n a1,a2,,an,表示第一个游戏每个关卡的通关时间;
第三行: n n n 个整数 b 1 , b 2 , … , b n b_1,b_2,\dots,b_n b1,b2,,bn,表示第二个游戏每个关卡的通关时间。

输出格式

单个整数:表示最多能通过多少关。

数据范围

对于 30 % 30\% 30% 的数据, 1 ≤ n ≤ 20 1\leq n\leq 20 1n20
对于 60 % 60\% 60% 的数据, 1 ≤ n ≤ 1000 1\leq n\leq 1000 1n1000
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 100000 1\leq n\leq 100000 1n100000 1 ≤ t ≤ 1 , 000 , 000 , 000 1\leq t\leq 1,000,000,000 1t1,000,000,000 1 ≤ a i , b i ≤ 10000 1\leq a_i, b_i\leq 10000 1ai,bi10000

样例数据

输入:

4 22
6 8 10 7 
7 11 9 9

输出:

3

说明:

选择通关678

分析1:直接爆搜!!

直接用搜索写

代码1:40分(老师代码,我那30分还是不拿出来吧)

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

int n,t,a[100010],b[100010],ans=0;

void dfs(int s,int x,int y){//简介的代码
	ans=max(ans,x+y);
	if (x!=n&&s+a[x+1]<=t) dfs(s+a[x+1],x+1,y);//升第一个的条件
	if (y!=n&&s+b[y+1]<=t) dfs(s+b[y+1],x,y+1);//升第二个的条件
}

int main(){
	cin>>n>>t;
	for(int i=1;i<=n;++i){
		cin>>a[i];
	}
	for(int i=1;i<=n;++i){
		cin>>b[i];
	}
	dfs(0,0,0);
	cout<< ans << endl;
	return 0;
}

分析2:搜索+记忆化

搜索加记忆化有效避免重复搜索

代码2:60分

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

int n,t,a[100010],b[100010],ans=0,dy[1010][1010];

void dfs(int s, int x, int y){
	if(s==dy[x][y]){//重复就不用看了
		return ;
	}else{
		dy[x][y]=s;//记每节点的结果
	}
	ans=max(ans,x+y);
	if (x!=n&&s+a[x+1]<=t) dfs(s+a[x+1],x+1,y);//升第一个的条件
	if (y!=n&&s+b[y+1]<=t) dfs(s+b[y+1],x,y+1);//升第二个的条件
}

int main(){
	cin>>n>>t;
	memset(dy,1,sizeof(dy));//memset只能赋值-1和0,赋值1的话会很大,利用这个特性把数组整大点
	for(int i=1;i<=n;++i){
		cin>>a[i];
	}
	for(int i=1;i<=n;++i){
		cin>>b[i];
	}
	dfs(0,0,0);
	cout<< ans << endl;
	return 0;
}

代码3:搜索换递推,效率嗷嗷往上涨

我们先把两个数据的前缀和求出来:

请添加图片描述

接着看第一组数据中的每一个前缀和与第二组数据的前缀和相比加能不能小于等于 t t t,能就加。直接缩成 O ( n 2 ) O(n^2) O(n2)

代码3:70分

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

int n,t,a[100010],b[100010],x[100010],y[100010],ans=0;

int main(){
	cin>>n>>t;
	for(int i=1;i<=n;++i){
		cin>>a[i];
		x[i]=x[i-1]+a[i];//前缀和
	}
	for(int i=1;i<=n;++i){
		cin>>b[i];
		y[i]=y[i-1]+b[i];//前缀和
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			if(x[i]+y[j]<=t){//相加与k比较
				ans=max(ans,i+j);
			}
		}
	}
	cout << ans << endl;
	return 0;
}

分析4:优化代码3

我们可以在上面代码的基础上第二组数据前缀和从后往前找,这样一找到就是最优解,可直接 b r e a k break break,并且前缀和数据是递增的,第一组数据一个比一个大,那第二组数据从后往前找就有浪费,所以开个变量记录一下找到的位置,下次寻找直接从变量位置开始寻找,优化成 O ( n + 1 ) O(n+1) O(n+1)

代码4:100分

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

int n,t,a[100010],b[100010],x[100010],y[100010],ans=0;

int main(){
	cin>>n>>t;
	for(int i=1;i<=n;++i){
		cin>>a[i];
		x[i]=x[i-1]+a[i];
	}
	for(int i=1;i<=n;++i){
		cin>>b[i];
		y[i]=y[i-1]+b[i];
	}
	int f=n;
	for(int i=0;i<=n;++i){
		for(int j=f;j>=0;--j){
			if(x[i]+y[j]<=t){
				f=j;
				ans=max(ans,i+j);
				break;
			}
		}
	}
	cout << ans << endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lyoi20210204

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值