2014年NOIP普及组T1-珠心算测验
题目描述
珠心算是一种通过在脑中模拟算盘变化来完成快速运算的一种计算技术。珠心算训练,既能够开发智力,又能够为日常生活带来很多便利,因而在很多学校得到普及。
某学校的珠心算老师采用一种快速考察珠心算加法能力的测验方法。他随机生成一个正整数集合,集合中的数各不相同,然后要求学生回答:其中有多少个数,恰好等于集合中另外两个(不同的)数之和?
最近老师出了一些测验题,请你帮忙求出答案。
输入格式
输入文件名为 count.in。
输入共两行,第一行包含一个整数 n,表示测试题中给出的正整数个数。
第二行有 n 个正整数,每两个正整数之间用一个空格隔开,表示测试题中给出的正整数。
输出格式
输出文件名为 count.out。
输出共一行,包含一个整数,表示测验题答案。
输入输出样例
输入样例1:
4 1 2 3 4
输出样例1:
2
说明
【样例说明】
由1+2=3,1+3=4,故满足测试要求的答案为2。注意,加数和被加数必须是集合中的两个不同的数。
【数据说明】
对于100%的数据,3 ≤ n ≤ 100,测验题给出的正整数大小不超过10,000
耗时限制1000ms 内存限制128MB
解析
考点:桶计数
参考代码
#include <bits/stdc++.h>
using namespace std;
//a:存储每个数
//r:标记哪些数出现过
int a[110],r[20010];
int ans = 0,n;
int main(){
cin>>n;
for(int i = 1;i <= n;i++){
cin>>a[i];
r[a[i]] = 1;//标记出现
}
//两两配对,检验有多少对 2 个数的和在数组中出现
for(int i = 1;i <= n;i++){
for(int j = i + 1;j <= n;j++){
//如果数对的和在数组中存在
if(r[a[i]+a[j]]==1){
ans++;
r[a[i]+a[j]] = 0;
}
}
}
cout<<ans;
return 0;
}
2014年NOIP普及组T2-比例简化
题目描述
在社交媒体上,经常会看到针对某一个观点同意与否的民意调查以及结果。例如,对某一观点表示支持的有1498 人,反对的有 902人,那么赞同与反对的比例可以简单的记为1498:902。
不过,如果把调查结果就以这种方式呈现出来,大多数人肯定不会满意。因为这个比例的数值太大,难以一眼看出它们的关系。对于上面这个例子,如果把比例记为5:3,虽然与真实结果有一定的误差,但依然能够较为准确地反映调查结果,同时也显得比较直观。
现给出支持人数A,反对人数B,以及一个上限L,请你将A比B化简为A’比B’,要求在A’和B’均不大于L且A’和B’互质(两个整数的最大公约数是1)的前提下,A’/B’ ≥ A/B且A’/B’ - A/B的值尽可能小。
输入格式
输入文件名为 ratio.in。
输入共一行,包含三个整数 A,B,L,每两个整数之间用一个空格隔开,分别表示支持人数、反对人数以及上限。
输出格式
输出文件名为 ratio.out。
输出共一行,包含两个整数 A’,B’,中间用一个空格隔开,表示化简后的比例。
输入输出样例
输入样例1:
1498 902 10
输出样例1:
5 3
说明
【数据范围】
对于100%的数据,1 ≤ A ≤ 1,000,000,1 ≤ B ≤ 1,000,000,1 ≤ L ≤ 100, A/B ≤ L。
耗时限制1000ms 内存限制128MB
解析
考点:穷举、转转相除法求最大公约数。
#include <iostream>
using namespace std;
int a,b,l;
int r1,r2;//找到的结果
double mi = 1e8;
//求两个数的最大公约数
int fun(int x,int y){
while(x % y != 0){
int t = x % y;
x = y;
y = t;
}
return y;
}
int main(){
cin>>a>>b>>l;
//枚举 A'和 B'的可能范围
for(int i = 1;i <= l;i++){
for(int j = 1;j <= l;j++){
double t = (i * b - j * a) * 1.0 / (j * b);
if(fun(i,j)==1 && t >= 0 && t < mi){
mi = t;
r1 = i;
r2 = j;
}
}
}
cout<<r1<<" "<<r2;
return 0;
}
2014年NOIP普及组T3-螺旋矩阵
题目描述
一个n行n列的螺旋矩阵可由如下方法生成:
从矩阵的左上角(第1行第1列)出发,初始时向右移动;如果前方是未曾经过的格子,则继续前进,否则右转;重复上述操作直至经过矩阵中所有格子。根据经过顺序,在格子中依次填入1, 2, 3, ... , n,便构成了一个螺旋矩阵。2
下图是一个n = 4 时的螺旋矩阵。
现给出矩阵大小 n 以及 i 和 j,请你求出该矩阵中第 i 行第 j 列的数是多少。
输入格式
输入文件名为 matrix.in。
输入共一行,包含三个整数 n,i,j,每两个整数之间用一个空格隔开,分别表示矩阵大小、待求的数所在的行号和列号
输出格式
输出文件名为 matrix.out。 输出共一行,包含一个整数,表示相应矩阵中第 i 行第 j 列的数
输入输出样例
输入样例1:
4 2 3
输出样例1:
14
说明
【数据范围】
对于 50%的数据,1 ≤ n ≤ 100;
对于 100%的数据,1 ≤ n ≤ 30,000,1 ≤ i ≤ n,1 ≤ j ≤ n。
耗时限制1000ms 内存限制128MB
解析
考点:二维数组
参考代码:
#include <bits/stdc++.h>
using namespace std;
/*
思路:
1.求解出,xy 点在第几圈
2.求解出这一圈之前出现了多少个点
*/
int n,k,ans,x,y;
int main(){
cin>>n>>x>>y;
//计算 xy 点在第几圈
k = min(min(x,y),min(n-x+1,n-y+1));
int w = n;//表示每一圈的边长
//计算在第 k 圈之前有多少个点出现过
for(int i = 1;i <= k - 1;i++){
ans += (w - 1) * 4;
w -= 2;
}
//向右
for(int j = k;j <= k + w - 1;j++){
ans++;
if(k == x && j == y){
cout<<ans;
return 0;
}
}
//向下
for(int i = k + 1;i <= k + w - 1;i++){
ans++;
if(i == x && k + w - 1 == y){
cout<<ans;
return 0;
}
}
//向左
for(int j = k + w - 2;j >= k;j--){
ans++;
if(k + w - 1 == x && j == y){
cout<<ans;
return 0;
}
}
//向上
for(int i = k + w - 2;i >= k + 1;i--){
ans++;
if(i == x && k == y){
cout<<ans;
return 0;
}
}
return 0;
}
更简单的写法
#include<bits/stdc++.h>
using namespace std;
int main(){
long long n,i,j,mi,ans=0;
cin>>n>>i>>j;
mi=min(i,min(j,min(n-i+1,n-j+1)));
if(i<=j)ans=mi*(4*(n-1)-4*mi)+10*mi-4*n-3+i+j;
else ans=mi*(4*n-4*mi)+2*mi+1-i-j;//模拟过程
cout<<ans;//输出ans
return 0;
}
2014年NOIP普及组T4-子矩阵
题目描述
给出如下定义:
1、子矩阵:从一个矩阵当中选取某些行和某些列交叉位置所组成的新矩阵(保持行与列的相对顺序)被称为原矩阵的一个子矩阵。
例如,下面左图中选取第2、4行和第2、4、5列交叉位置的元素得到一个2*3的子矩阵如右图所示。
2、相邻的元素:矩阵中的某个元素与其上下左右四个元素(如果存在的话)是相邻的。
3、矩阵的分值:矩阵中每一对相邻元素之差的绝对值之和。
本题任务:给定一个n行m列的正整数矩阵,请你从这个矩阵中选出一个r行c列的子矩阵,使得这个子矩阵的分值最小,并输出这个分值。
输入格式
第一行包含用空格隔开的四个整数 n,m,r,c,意义如问题 述中所述,每两个整数之间用一个空格隔开。
接下来的 n 行,每行包含 m 个用空格隔开的整数,用来表示问题 述中那个 n 行 m 列的矩阵。
输出格式
输出共 1 行,包含 1 个整数,表示满足题目 述的子矩阵的最小分值。
输入输出样例
输入样例1:
5 5 2 3 9 3 3 3 9 9 4 8 7 4 1 7 4 6 6 6 8 5 6 9 7 4 5 6 1
输出样例1:
6
输入样例2:
7 7 3 3 7 7 7 6 2 10 5 5 8 8 2 1 6 2 2 9 5 5 6 1 7 7 9 3 6 1 7 8 1 9 1 4 7 8 8 10 5 9 1 1 8 10 1 3 1 5 4 8 6
输出样例2:
16
输入样例3:
7 7 3 3 7 7 7 6 2 10 5 5 8 8 2 1 6 2 2 9 5 5 6 1 7 7 9 3 6 1 7 8 1 9 1 4 7 8 8 10 5 9 1 1 8 10 1 3 1 5 4 8 6
输出样例3:
16
说明
【输入输出样例1说明】
该矩阵中分值最小的2行3列的子矩阵由原矩阵的第4行、第5行与第1列、第3列、第4列交叉位置的元素组成,为
6 5 6
7 5 6
,其分值为
|6−5| + |5−6| + |7−5| + |5−6| + |6−7| + |5−5| + |6−6| =6。
【输入输出样例2说明】
该矩阵中分值最小的3行3列的子矩阵由原矩阵的第4行、第5行、第6行与第2列、第6列、第7列交叉位置的元素组成,选取的分值最小的子矩阵为
9 7 8 9 8 8 5 8 10
【数据说明】
对于50%的数据,1 ≤ n ≤ 12,1 ≤ m ≤ 12,矩阵中的每个元素1 ≤ a[i][j] ≤ 20;
对于100%的数据,1 ≤ n ≤ 16,1 ≤ m ≤ 16,矩阵中的每个元素1 ≤ a[i][j] ≤ 1,000,1 ≤ r ≤ n,1 ≤ c ≤ m。
耗时限制1000ms 内存限制128MB
解析
考点:深搜+动态规划
思路:
(1)先通过搜索确认选中了哪些行;
(2)再 DP 求出 f[i][j],也就是以第 i 列结尾, 选择 j 列的最低分数。 为方便求解分数,使用:
v1[j]代表: 第 j 的得分。
v2[i][j]代表:第 i 列和第 j 列两者之间的分数。
因此可得状态转移方程如下:
f[i][j] = min(f[i][j],f[k][j-1]+v1[i]+v2[k][i])
参考代码
#include<bits/stdc++.h>
using namespace std;
const int N = 30;
int a[N][N],f[N][N],v1[N],v2[N][N];
int n,m,r,c;
int row[N];//代表选中了哪些行
int ans = INT_MAX;
//计算 i 列中,以第 i 列结尾,选择 j 列的最小得分
void dp(){
//由于 dp 要做多次,每次要初始化如下三个数组
memset(v1,0,sizeof(v1));
memset(v2,0,sizeof(v2));
memset(f,0x3f,sizeof(f));
//计算每一列的值
for(int j = 1; j <= m; j++){
for(int i = 1; i <= r - 1; i++){
v1[j] += abs(a[row[i]][j]-a[row[i+1]][j]);
}
}
//计算两两列之间的值
for(int i = 1; i <= m; i++){
for(int j = 1; j <= m; j++){
for(int k = 1; k <= r; k++){
v2[i][j] += abs(a[row[k]][i]-a[row[k]][j]);
}
}
}
//dp 计算 f[i][j]
for(int i = 1; i <= m; i++){
for(int j = 1; j <= min(i,c); j++){
//注意边界,如果只选了一列,获取该列的分
if(j == 1){
f[i][j] = min(f[i][j],v1[i]);
if(c == 1) ans = min(ans,f[i][j]);
continue;
}
//循环 k 的范围
for(int k = j - 1; k <= i - 1; k++){
f[i][j] = min(f[i][j],f[k][j-1]+v1[i]+v2[k][i]);
}
if(j == c) ans = min(ans,f[i][j]);
}
}
}
//讨论到了第 x 行,当前选中了 cnt 行
void dfs(int x,int cnt){
//从 n 行选满了 r 行
if(cnt == r){
//动归计算出 f[i][j]的最小值
dp();
return;
}
if(x > n) return;
row[cnt + 1] = x;
//如果第 x 行被选中
dfs(x+1,cnt+1);
//如果第 x 行没有被选中
dfs(x+1,cnt);
}
int main(){
cin>>n>>m>>r>>c;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
cin>>a[i][j];
}
}
//从 n 行中搜索出 r 行
dfs(1,0);
cout<<ans;
return 0;
}