day01:三大结构&数组&数组排序&查找(顺序查找&二分查找)
- day01:三大结构&数组&数组排序&查找(顺序查找&二分查找)
- 1.P3954 [NOIP2017 普及组] 成绩
- 2. P1085 [NOIP2004 普及组] 不高兴的津津
- 3. P1046 [NOIP2005 普及组] 陶陶摘苹果
- 4. P1980 [NOIP2013 普及组] 计数问题
- 5. P1035 [NOIP2002 普及组] 级数求和
- 6.P1008 [NOIP1998 普及组] 三连击
- 7. P1047 [NOIP2005 普及组] 校门外的树
- 8. P1059 [NOIP2006 普及组] 明明的随机数
- 9.P2669 [NOIP2015 普及组] 金币
- 10.P1909 [NOIP2016 普及组] 买铅笔
- 11. P1014 [NOIP1999 普及组] Cantor 表
day01:三大结构&数组&数组排序&查找(顺序查找&二分查找)
三大结构:
顺序结构:从上至下,从左至右的执行
分支结构:满足flag为真就执行
if(flag){}
if(flag){}
else{}
if(flag){}
else if(flag){}
else if(flag){}
else{}
循环结构:满足flag为真就一直执行
for(int i=0; i<n; i++){}
while(flag){}
do{}while(flag);
数组
数组是同类数据的一个集合,开辟的是一段连续的内存空间,
二维数组的下一行接着上一行的末尾。
初始化方式
int a[10];
int a[10]={0,1,2,3,4,5,6,7,8,9};
int a[10]={0,1,2,3};
const int N=10;
int a[N];
int类型,全局变量默认初始化为0,
局部变量如果未初始化,则初始化为随机数,
局部变量如果部分初始化,则未初始化部分为0,
数组排序:
sort(a,a+n);//#include<algorithm>
sort(a, a+n); //sort(首地址,首地址+元素个数)
sort(a, a+n, cmp);//自定义比较函数,修改排序规则
sort() 默认升序排列,内部封装快速排序方法,时间复杂度 nlogn
选择排序
每一次选择最小的元素放在未排序的最前面,时间复杂度:O(n^2)
for(int i=0; i<n-1; i++){//选择排序
for(int j=i+1; j<n; j++){
if(a[i]>a[j]) {
swap(a[i], a[j]);//交换 a[i],a[j]
}
}
}
归并排序(暂作了解):分治&归并(作为最后的一个补充)
#include<bits/stdc++.h>
using namespace std;
/*
归并排序:分治思想,先分小,再治小(排序),最后合并
在所有排序中,归并排序最为稳定,时间复杂为 logn,也就是log2(n)
*/
int a[10]= {3,4,5,6,2,1,6,7,8,9};
int n=sizeof(a)/sizeof(int); //通过数组大小计算元素个数
void merge(int arr[], int l, int mid, int r) {
int i=l, j=mid+1, t=0, temp[r];//开一个临时数组,存放答案
while(i<=mid && j<=r) {
if(arr[i]<=arr[j]) temp[t++] = arr[i++];
else temp[t++] = arr[j++];
}
while(i<=mid) temp[t++] = arr[i++];//处理剩余元素
while(j<=r) temp[t++] = arr[j++];
t = 0;
while(l<=r) arr[l++] = temp[t++];//将答案赋给原数组
}
//int *arr 在这里等同于 int a[],不过是使用指针的格式进行传参
void MergeSort(int * arr,int l, int r) {
if(l<r) {
int mid = (l+r)/2; //取中间作为分割点
MergeSort(arr, l, mid); //分左边
MergeSort(arr, mid+1, r); //分右边
merge(arr, l, mid, r); //左右合并
}
}
int main() {
MergeSort(a, 0, n-1);//自定义排序函数,MergeSort(数组首地址,排序起始下标,排序结束下标);
for(int i=0; i<n; i++) {
cout<<a[i]<<" ";
}
return 0;
}
数组是一段连续的内存空间,下标可以超过内存空间,且不会报错,但是结果不对
二维数组的下一行与上一行的末尾是相邻的
数组下标的灵活使用:int a[4][5];
00 01 02 03 04
10 11 12 13 14
20 21 22 23 24
30 31 32 33 34
ij: i表示行,j表示列,同行i不变,同列j不变
左上角到右下角对角线:j-i是定值
左下角到右上角对角线:i+j是定值
查找(普通查找&二分查找)
普通查找其实就是暴力枚举,直到找到目标
如:给你n个数据,存放在数组a[n]中,保证每个数不同,请找出某个数在该数组的位置
for(int i=0; i<n; i++){
if(a[i]==x){
//i即为所求
}
}
二分查找是基于一个已经排好序的数列,在其中查找某个值
如:给你一个数列:int a[10]={0,1,2,3,4,5,6,7,8,9};
快速查找x
在数列中的位置
int a[10]= {0,1,2,3,4,5,6,7,8,9};
int l=0, r=sizeof(a)/sizeof(int)-1;
int x=3; //cin>>x;
while(l<=r){ //满足左下标小于等于右下标的前提
int mid = (l+r)/2;
if(a[mid]<x){//中间的数比x小,则x在右半部分
l = mid+1;
}else if(a[mid]>x){//中间的数比x大,则x在左半部分
r=mid-1;
} else{
cout<<mid; break;//找到答案,输出并退出循环
}
}
1.P3954 [NOIP2017 普及组] 成绩
【题目描述】牛牛最近学习了C++入门课程,这门课程的总成绩计算方法是:
总成绩=作业成绩×20%+小测成绩×30%+期末考试成绩×50%
牛牛想知道,这门课程自己最终能得到多少分。
输入格式:
三个非负整数A,B,C,分别表示牛牛的作业成绩、小测成绩和期末考试成绩。
相邻两个数之间用一个空格隔开,三项成绩满分都是100分。
输出格式:一个整数,即牛牛这门课程的总成绩,满分也是100分。
输入样例:100 100 80
输出样例:90
题解:这就是一个顺序结构,但是注意涉及到小数的要不要用double
#include<bits/stdc++.h>//万能头文件
using namespace std;
int main() {
int a,b,c; cin>>a>>b>>c;
cout<<a*0.2+b*0.3+c*0.5;
return 0;
}
2. P1085 [NOIP2004 普及组] 不高兴的津津
【题目描述】津津上初中了。妈妈认为津津应该更加用功学习,所以津津除了上学之外,还要参加妈妈为她报名的各科复习班。另外每周妈妈还会送她去学习朗诵、舞蹈和钢琴。但是津津如果一天上课超过八个小时就会不高兴,而且上得越久就会越不高兴。假设津津不会因为其它事不高兴,并且她的不高兴不会持续到第二天。请你帮忙检查一下津津下周的日程安排,看看下周她会不会不高兴;如果会的话,哪天最不高兴。
输入格式:
输入包括7行数据,分别表示周一到周日的日程安排。
每行包括两个小于10的非负整数,用空格隔开,
分别表示津津在学校上课的时间和妈妈安排她上课的时间。
输出格式:一个数字。
如果不会不高兴则输出0,如果会则输出最不高兴的是周几(用
1, 2, 3, 4, 5, 6, 7分别表示周一,周二,周三,周四,周五,周六,周日)。
如果有两天或两天以上不高兴的程度相当,则输出时间最靠前的一天。
输入样例:
5 3
6 2
7 2
5 3
5 4
0 4
0 6
输出样例:3
题解:就是求那天的和值最大,最大第一次出现的位置
#include<bits/stdc++.h>
using namespace std;
int main() {
int max=0; //最多上课时间
int k=0; //最不高兴的那一天
for(int i=1; i<=7; i++) {
int a,b; cin>>a>>b;
if(a+b>max && a+b>8) {
max = a+b; k = i;
}
}
cout<<k; return 0;
}
3. P1046 [NOIP2005 普及组] 陶陶摘苹果
【题目描述】陶陶家的院子里有一棵苹果树,每到秋天树上就会结出 10 个苹果。苹果成熟的时候,陶陶就会跑去摘苹果。陶陶有个 30 厘米高的板凳,当她不能直接用手摘到苹果的时候,就会踩到板凳上再试试。
现在已知 10 个苹果到地面的高度,以及陶陶把手伸直的时候能够达到的最大高度,请帮陶陶算一下她能够摘到的苹果的数目。假设她碰到苹果,苹果就会掉下来。
输入格式:输入包括两行数据((全部数据以厘米为单位))
第一行包含10个100到200之间(包括100和200)的整数分别表示10个苹果到地面的高度,两个相邻的整数之间用一个空格隔开。
第二行只包括一个100到120之间(包含100和120)的整数,表示陶陶把手伸直的时候能够达到的最大高度。
输出格式:输出一个整数,表示陶陶能够摘到的苹果的数目。
输入样例:
100 200 150 140 129 134 167 198 200 111
110
输出样例:5
题解:注意只要碰到就行,所以苹果高度<= 能摸到的最大高度 即可
#include<bits/stdc++.h>
using namespace std;
int main(){
int a[10];
for(int i=0; i<10; i++) cin>>a[i];
int h, ans=0; cin>>h;
for(int i=0; i<10; i++){
if(a[i]<=h+30){//注意要等于
ans++;
}
}
cout<<ans; return 0;
}
4. P1980 [NOIP2013 普及组] 计数问题
【题目描述】试计算在区间 1 到 n 的所有整数中,数字x(0 ≤ x ≤ 9)共出现了多少次?
例如,在 1到11中,即在1,2,3,4,5,6,7,8,9,10,11 中,数字 11 出现了 44 次。
输入格式:2个整数n,x,之间用一个空格隔开。
输出格式:1个整数,表示x出现的次数。
输入样例:11 1
输出样例:4
题解:对1到n,进行一次枚举,同时判断枚举数据中的x个数
#include<bits/stdc++.h>
using namespace std;
int main(){ //n=1e6 = 1000000
int n,x, ans=0; cin>>n>>x;
for(int i=1; i<=n; i++){
int temp=i; // 临时变量
while(temp!=0){
if(temp%10==x) ans++; //如果个位数等于x,则答案+1
temp /= 10;// temp = temp/10; 舍去个位数
}
}
cout<<ans; return 0;
}
5. P1035 [NOIP2002 普及组] 级数求和
【题目描述】已知:Sn= 1+1/2+1/3+…+1/n。显然对于任意一个整数 k,当 n 足够大的时候,Sn>k。
现给出一个整数 k,要求计算出一个最小的 n,使得 Sn>k。
输入格式:一个正整数 k
输出格式:一个正整数 n
输入样例:1
输出样例:2
题解:注意大坑:需要开double(小数后15位有效),float(小数后7位有效)会出bug。
#include<bits/stdc++.h>
using namespace std;
int main(){
int k,n=0; cin>>k;
double s=0;//注意double
while(s<=k){
++n; s += 1.0/n;//公式
}
cout<<n; return 0;
}
6.P1008 [NOIP1998 普及组] 三连击
【题目描述】将1,2,…,9 共 9 个数分成 3 组,分别组成 3个三位数,且使这3个三位数构成 1 : 2 : 3的比例,试求出所有满足条件的3 个三位数。
输入格式:无输入
输出格式:若干行,每行 3 个数字。按照每行第1 个数字升序排列。
输入样例:无
输出样例:
192 384 576
* * *
...
* * *(剩余部分不予展示)
题解:注意使用技巧 -- 打标记,如果数字 i 出现了,那么 t[i]=1,否则 t[i] =0;
#include<bits/stdc++.h>
using namespace std;
int main(){
for(int i=100; i<=333; i++){
int a=i, b=2*i, c=3*i;
int t[10]={0}, sum=0;//对1-9进行计数,出现过则为1,未出现则为0
//memset(t, 0, sizeof(t)); //初始化为0
t[a/100]=1, t[a/10%10]=1, t[a%10]=1;
t[b/100]=1, t[b/10%10]=1, t[b%10]=1;
t[c/100]=1, t[c/10%10]=1, t[c%10]=1;
for(int j=1; j<=9; j++) sum += t[j];
if(sum==9) cout<<a<<" "<<b<<" "<<c<<endl;
} return 0;
}
7. P1047 [NOIP2005 普及组] 校门外的树
【题目描述】某校大门外长度为 l 的马路上有一排树,每两棵相邻的树之间的间隔都是 1 米。我们可以把马路看成一个数轴,马路的一端在数轴 0 的位置,另一端在 l 的位置;数轴上的每个整数点,即 0,1,2,3,..., l 都种有一棵树。
由于马路上有一些区域要用来建地铁。这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。现在要把这些区域中的树(包括区域端点处的两棵树)移走。你的任务是计算将这些树都移走后,马路上还有多少棵树。
输入格式:第一行有两个整数,分别表示马路的长度 l 和区域的数目 m。
接下来 m 行,每行两个整数 u, v,表示一个区域的起始点和终止点的坐标。
输出格式:输出一行一个整数,表示将这些树都移走后,马路上剩余的树木数量。
输入样例:
500 3
150 300
100 200
470 471
输出样例:298
题解:也是一个打标记的小技巧
#include<bits/stdc++.h>
using namespace std;
const int N=1e4;
int a[N];//0表示有树,1表示无树
int main(){
int l,m; cin>>l>>m;
for(int i=1; i<=m; i++){
int u,v; cin>>u>>v;
for(int j=u; j<=v; j++) a[j]=1;//树被移栽了
}
int ans=0;
for(int i=0; i<=l; i++)
if(a[i]==0) ans++;//树木个数+1
cout<<ans; return 0;
}
8. P1059 [NOIP2006 普及组] 明明的随机数
【题目描述】明明想在学校中请一些同学一起做一项问卷调查,为了实验的客观性,他先用计算机生成了N个1到1000之间的随机整数(N≤100),对于其中重复的数字,只保留一个,把其余相同的数去掉,不同的数对应着不同的学生的学号。然后再把这些数从小到大排序,按照排好的顺序去找同学做调查。请你协助明明完成“去重”与“排序”的工作。
输入格式:
输入有两行,第1行为1个正整数,表示所生成的随机数的个数N
第2行有N个用空格隔开的正整数,为所产生的随机数。
输出格式:
输出也是两行,第1行为1个正整数M,表示不相同的随机数的个数。
第2行为M个用空格隔开的正整数,为从小到大排好序的不相同的随机数。
输入样例:
10
20 40 32 67 40 20 89 300 400 15
输出样例:
8
15 20 32 40 67 89 300 400
题解:
#include<bits/stdc++.h>
using namespace std;
const int N=1e4;
int a[N], b[N], cnt=0;
int main(){//解法1 - 桶排序的思想
int n; cin>>n;
for(int i=1; i<=n; i++){
int x; cin>>x;
a[x]=1;
}
for(int i=1; i<=1000; i++){
if(a[i]==1) b[++cnt]=i;
}
cout<<cnt<<endl;
for(int i=1; i<=cnt; i++) cout<<b[i]<<" ";
return 0;
}
int main_ac(){//解法2 - 模拟一遍,先排序,再去重
int n; cin>>n;
for(int i=0; i<n; i++) cin>>a[i];
sort(a, a+n);//升序排序
b[0]=a[0];
for(int i=0; i<n-1; i++){
if(b[cnt]!=a[i+1]){
b[++cnt] = a[i+1];
}
}
cout<<cnt+1<<endl;
for(int i=0; i<=cnt; i++) cout<<b[i]<<' ';
return 0;
}
9.P2669 [NOIP2015 普及组] 金币
【题目描述】国王将金币作为工资,发放给忠诚的骑士。第一天,骑士收到一枚金币;之后两天(第二天和第三天),每天收到两枚金币;之后三天(第四、五、六天),每天收到三枚金币;之后四天(第七、八、九、十天),每天收到四枚金币……;这种工资发放模式会一直这样延续下去:当连续 n天每天收到 n枚金币后,骑士会在之后的连续 n+1天里,每天收到 n+1 枚金币。
请计算在前 k天里,骑士一共获得了多少金币。
输入格式:一个正整数 k,表示发放金币的天数。
输出格式:一个正整数,即骑士收到的金币数。
输入样例:6
输出样例:14
题解:主要是将题目信息归纳出来,学会如何归纳总结,养成用数据表达题意的习惯。
/* 根据题目可列出下表,找到规律
1
2 2
3 3 3
4 4 4 4
....*/
#include<bits/stdc++.h>
using namespace std;
int main(){
int k,sum=0; cin>>k;
int i=1;
while(k>0){
for(int j=1; j<=i && k>0; j++){
sum += i; k--;
}
i++;
}
cout<<sum; return 0;
}
推荐:P1014 [NOIP1999 普及组] Cantor 表 这道题目也是同样类型的题目,只是分了左右两个方向
10.P1909 [NOIP2016 普及组] 买铅笔
【题目描述】P老师需要去商店买n支铅笔作为小朋友们参加NOIP的礼物。她发现商店一共有 33种包装的铅笔,不同包装内的铅笔数量有可能不同,价格也有可能不同。为了公平起 见,P老师决定只买同一种包装的铅笔。
商店不允许将铅笔的包装拆开,因此P老师可能需要购买超过n支铅笔才够给小朋友们发礼物。现在P老师想知道,在商店每种包装的数量都足够的情况下,要买够至少n支铅笔最少需要花费多少钱。
输入格式:
第一行包含一个正整数n,表示需要的铅笔数量。
接下来三行,每行用2个正整数描述一种包装的铅笔:
其中第1个整数表示这种包装内铅笔的数量,第2个整数表示这种包装的价格。
保证所有的7个数都是不超过10000的正整数。
输出格式:1个整数,表示P老师最少需要花费的钱。
输入样例:
57
2 2
50 30
30 27
输出样例:54
题解:题目要求用最少的钱达到目的即可,所以先设money极大,依次取当前方案的最小值即可。
#include<bits/stdc++.h>
using namespace std;
int main(){
int n, money=1e8; cin>>n;
for(int i=1; i<=3; i++){
int num, price; cin>>num>>price;
if(money>ceil(1.0*n/num)*price)//最便宜
money=ceil(1.0*n/num)*price;
}
cout<<money; return 0;
}
11. P1014 [NOIP1999 普及组] Cantor 表
【题目描述】
现代数学的著名证明之一是 Georg Cantor 证明了有理数是可枚举的。他是用下面这一张表来证明这一命题的:
1/1, 1/2, 1/3, 1/4, 1/5, …
2/1, 2/2, 2/3, 2/4, …
3/1, 3/2, 3/3, …
4/1, 4/2, …
5/1, …
…
我们以 Z 字形给上表的每一项编号。第一项是 1/1,然后是1/2,2/1,3/1,2/2,…
输入格式:整数N(1≤N≤107)。
输出格式:表中的第 N项。
输入样例:7
输出样例:1/4
题解:本题属于找规律的题目,要学会用数据来展示规律
#include<bits/stdc++.h>
using namespace std;
/* 找规律:
1/1
2/1 1/2
3/1 2/2 1/3
4/1 3/2 2/3 1/4
5/1 4/2 3/3 2/4 1/5
确定第n个数所在行列:i,j
奇数行:左往右数
偶数行:右往左数
并且:x+y=i+1 */
int main() {
int i=0,j=0,sum=0,x=1,y=1;
int n; cin>>n;
while(sum<n){
i++;
sum += i;
}
j = sum-n+1;
if(i%2!=0) x=j, y=i+1-x;//奇数,左往右数
else x=i-j+1, y=i+1-x; //偶数,右往左数
cout<<x<<"/"<<y<<endl; return 0;
}