有趣解题记录

以下是题目1:

# [NOIP2014 普及组] 珠心算测验

## 题目背景

NOIP2014 普及 T1

## 题目描述

珠心算是一种通过在脑中模拟算盘变化来完成快速运算的一种计算技术。珠心算训练,既能够开发智力,又能够为日常生活带来很多便利,因而在很多学校得到普及。


某学校的珠心算老师采用一种快速考察珠心算加法能力的测验方法。他随机生成一个正整数集合,集合中的数各不相同,然后要求学生回答:其中有多少个数,恰好等于集合中另外两个(不同的)数之和?


最近老师出了一些测验题,请你帮忙求出答案。

## 输入格式

共两行,第一行包含一个整数 $n$,表示测试题中给出的正整数个数。


第二行有 $n$ 个正整数,每两个正整数之间用一个空格隔开,表示测试题中给出的正整数。

## 输出格式

一个整数,表示测验题答案。

## 样例 #1

### 样例输入 #1

```
4
1 2 3 4
```

### 样例输出 #1

```
2
```

## 提示

【样例说明】


由 $1+2=3,1+3=4$,故满足测试要求的答案为 $2$。  

注意,加数和被加数必须是集合中的两个不同的数。


【数据说明】


对于 $100\%$ 的数据,$3 \leq n \leq 100$,测验题给出的正整数大小不超过 $10,000$。

接下来是自己一开始的分析:

就是枚举嘛,然后用数组存下我们输入的n个正整数,请注意是正整数,一个小小的细节很大的作用。

继续往下看,因为一开始想着从1到n挨着判断他是否等于所有数中其他两个不同数的和嘛,所以这题是需要三个循环来实现的,以下是我一开始的代码:

#include<iostream>
using namespace std;
int n,a[101];
int ans=0;
int main()
{ 
    cin>>n;
	for(int i=1;i<=n;i++)
    cin>>a[i];
    for(int i=1;i<=n;i++) {
    	bool ok=false;
    	for(int j=1;j<=n;j++)
    	{
    		for(int k=1;k<=n;k++)
    		if(i!=j&&j!=k&&i!=k)
			if(a[i]==a[j]+a[k])
            ok=true;
		}
		if(ok) ans++;
	}
	cout<<ans<<endl;
	return 0;
}

很明显就是没有去思考细节暴力的解法:

可是实际上,这个代码是可以优化时间复杂度的,第一由于每个都是正正数,那么当i,j,k任意两个数相等 就已经说明等式不可能成立了,因为等式一方必定多一个正整数,其次当循环到某个位置ok已经true了之后,就可以直接不循环直接ans++了,那么优化后是这样:

#include<iostream>
using namespace std;
int main()
{
	int n;
	cin>>n;
	int ans=0;
	int a[101];
	for(int i=1;i<=n;i++)
		cin>>a[i];
	for(int i=1;i<=n;i++)
	{bool ok=false;
		for(int j=1;j<=n && !ok;j++)
			for(int k=j+1;k<=n && !ok;k++)
				 	if(a[i]==a[j]+a[k])
				ok=true;
			if(ok) ++ans;
	}
	cout<<ans<<endl;
	return 0;	
}

这样当ok已经true的时候不必再继续进行循环浪费时间并且代码量减少更加的清晰了,而且去掉了一个if,因为就算ijk三者任二相等,等式也不可能成立,因为等式左右两边的数字都是正整数嘛,同时在第三个for里面将k的初始值定为j+1也能减少循环次数,这样当n的值很大的时候所降低的时间复杂度尤为明显。

题目2:

# [NOIP2010 普及组] 接水问题

## 题目描述

学校里有一个水房,水房里一共装有 $m$ 个龙头可供同学们打开水,每个龙头每秒钟的供水量相等,均为 $1$。

现在有 $n$ 名同学准备接水,他们的初始接水顺序已经确定。将这些同学按接水顺序从 $1$ 到 $n$ 编号,$i$ 号同学的接水量为 $w_i$。接水开始时,$1$ 到 $m$ 号同学各占一个水龙头,并同时打开水龙头接水。当其中某名同学 $j$ 完成其接水量要求 $w_j$ 后,下一名排队等候接水的同学 $k$ 马上接替 $j$ 同学的位置开始接水。这个换人的过程是瞬间完成的,且没有任何水的浪费。即 $j$ 同学第 $x$ 秒结束时完成接水,则 $k$ 同学第 $x+1$ 秒立刻开始接水。若当前接水人数 $n'$ 不足 $m$,则只有 $n'$ 个龙头供水,其它 $m - n'$ 个龙头关闭。

现在给出 $n$ 名同学的接水量,按照上述接水规则,问所有同学都接完水需要多少秒。

## 输入格式

第一行两个整数 $n$ 和 $m$,用一个空格隔开,分别表示接水人数和龙头个数。

第二行 $n$ 个整数 $w_1,w_2,\ldots,w_n$,每两个整数之间用一个空格隔开,$w_i$ 表示 $i$ 号同学的接水量。

## 输出格式

一个整数,表示接水所需的总时间。

## 样例 #1

### 样例输入 #1

```
5 3
4 4 1 2 1
```

### 样例输出 #1
4
8 4
23 71 87 32 70 93 80 76

### 样例输出 #2
163

## 提示

【输入输出样例 \#1 说明】

第 $1$ 秒,$3$ 人接水。第 $1$ 秒结束时,$1,2,3$ 号同学每人的已接水量为 $1,3$ 号同学接完水,$4$ 号同学接替 $3$ 号同学开始接水。

第 $2$ 秒,$3$ 人接水。第 $2$ 秒结束时,$1,2$ 号同学每人的已接水量为 $2,4$ 号同学的已接水量为 $1$。

第 $3$ 秒,$3$ 人接水。第 $3$ 秒结束时,$1,2$ 号同学每人的已接水量为 $3,4$ 号同学的已接水量为 $2$。$4$ 号同学接完水,$5$ 号同学接替 $4$ 号同学开始接水。

第 $4$ 秒,$3$ 人接水。第 $4$ 秒结束时,$1,2$ 号同学每人的已接水量为 $4,5$ 号同学的已接水量为 $1$。$1,2,5$ 号同学接完水,即所有人完成接水的总接水时间为 $4$ 秒。

【数据范围】

$1 \le n \le {10}^4$,$1 \le m \le 100$,$m \le n$;

$1 \le w_i \le 100$。

第一次做的时候,代码如下(每一行配备了理由)

#include<iostream>
using namespace std;
int n,m;//代表接水人数和水龙头数
int w[10001],c[101];//前者存储编号从一到n的同学的接水时间,后者则用来定义初始龙头的使用以及龙头的更新情况
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	cin>>w[i];//存入每位同学需要接多少水
	for(int i=1;i<=m;i++)
	c[i]=w[i];//一开始龙头对应编号同学的接水时间
	int x=m+1;//用来代表排队的第一名同学
	for(int i=1;;i++){
		for(int j=1;j<=m;j++)
		if(c[j]) c[j]--;//如果还在接水消耗一秒
		for(int j=1;j<=m;j++)
			if(!c[j] && x<=n){//如果从【j】为0并且还有人排队那么就更新龙头的使用者
				c[j]=w[x];
				++x;//第一名排队的同学改变了
			}
			bool ok=true;//假设这时候所有同学接完水
			for(int k=1;k<=m;k++)
			if(c[k]) ok=false;//从一枚举到m假设有一个龙头对应前有同学还在接水则ok为false
			if(ok) {
			cout<<i<<endl;//否则输出消耗的秒数
			return 0;}
    }
}

 事实上,这道题还有更加简单的做法,话不多说直接上代码:

#include<iostream>
using namespace std;
int n,m;
int w[10001],c[101];
int main(){
   cin>>n>>m;
   for(int i=1;i<=n;i++)
    cin>>w[i];
    for(int i=1;i<=m;i++)
    c[i]=w[i];//到这里为止与前面没有区别
    int ans=0;//先令答案为0
    for(int i=m+1;i<=n;i++){//进行n-(m+1)+1次操作
    	int pos=1;//假设第一个龙头面前的学生接水时间最少
    	for(int j=2;j<=m;j++)
    	if(c[j]<c[pos])
    	pos=j;//如果假设不成立那么pos就对应接水时间最少的同学
    	c[pos]+=w[i];//每一次枚举这个接水时间最少的对应龙头都要加上排队第一个同学的接水时间
    	ans=max(ans,c[pos]);//在n-(m+1)+1次操作中不断判断哪一个龙头用来接水的时间最长
	}
	cout<<ans<<endl;//输出答案
	return 0;
}

根据题目的意思实际上可以通过判断哪一个水龙头对应的接水时长最久,最久的那个就对应了要求的答案,这样写就降低了时间复杂度与代码量同时思路也更为简便。

接下来看一道洛谷上面的题目,实际上只是洛谷的一道入门题目,但是却把愚昧的我困惑了蛮久,后来才发现棋盘上的坐标的规律,以下是题目:

代码如下:

#include<iostream>
using namespace std;
int main()
{
    int n,x,y;
    cin>>n>>x>>y;
    for(int i=1;i<=n;i++){
        cout<<"("<<x<<","<<i<<")"<<" ";    	
    }
    cout<<endl;
    for(int i=1;i<=n;i++){
        cout<<"("<<i<<","<<y<<")"<<" ";    	
    }
    cout<<endl;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
		    if(i-j==x-y){
		        cout<<"("<<i<<","<<j<<")"<<" ";	
		    }
	    }
    }
    cout<<endl;
    for(int i=n;i>=1;i--){
        for(int j=1;j<=n;j++){
		    if(i+j==x+y){
		        cout<<"("<<i<<","<<j<<")"<<" ";	
		    }
	    }
    }
    cout<<endl;
	return 0;
}

 

  • 9
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

残念亦需沉淀

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

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

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

打赏作者

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

抵扣说明:

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

余额充值