【真题讲解】 NOIP2014

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Ronaldo7_ZYB/article/details/82914705

T1:珠心算测验

题目描述
珠心算是一种通过在脑中模拟算盘变化来完成快速运算的一种计算技术。珠心算训练, 既能够开发智力,又能够为日常生活带来很多便利,因而在很多学校得到普及。
某学校的珠心算老师采用一种快速考察珠心算加法能力的测验方法。他随机生成一个正 整数集合,集合中的数各不相同,然后要求学生回答:其中有多少个数,恰好等于集合中另 外两个(不同的)数之和?
最近老师出了一些测验题,请你帮忙求出答案。
输入格式
输入文件名为 count.in
输入共两行,第一行包含一个整数 n,表示测试题中给出的正整数个数。
第二行有 n 个正整数,每两个正整数之间用一个空格隔开,表示测试题中给出的正整数。
输出格式
输出文件名为 count.out。
输出共一行,包含一个整数,表示测验题答案。
样例数据
input
4
1 2 3 4
output
2
样例说明
由 1+2=3,1+3=4,故满足测试要求的答案为 2。注意,加数和被加数必须是集合中的两个不同的数。
数据规模与约定
对于 100%的数据,3 ≤ n ≤ 100,测验题给出的正整数大小不超过 10,000。
时间限制:1s1s
空间限制:128MB

本题的题意容易产生误解,意为有多少个数可以被其它的数字所加,而不是求加的所有方案。例如,3,1,2,1,2这一串数字,3同时可以同时被答案所加,但是只能算以此,这也是容易得到30分的地方。

此题的做法是:
1. 1. 读入。
2. 2. 双重循环枚举两个数,将两数之和进行标记。
3. 3. 枚举每一个数,看这个数是否已经被标记即可。

因自己代码不变理解,此处放上他人代码,如下:

#include<bits/stdc++.h>
using namespace std;
int a[10001]={},b[10000001]={};
int ans=0;
int n;
int main()
{
    freopen("count.in","r",stdin);
    freopen("count.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for (int i=1;i<=n;i++)
        for (int j=i+1;j<=n;j++)
            b[a[i]+a[j]]=1;
    for (int i=1;i<=n;i++) 
        ans+=b[a[i]];
    printf("%d",ans);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

2.比例简化

题目描述
在社交媒体上,经常会看到针对某一个观点同意与否的民意调查以及结果。例如,对某 一观点表示支持的有 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’,中间用一个空格隔开,表示化简后的比例。
样例数据
input
1498 902 10
output
5 3
数据规模与约定
对于 100%的数据,1 ≤ A ≤ 1,000,000,1 ≤ B ≤ 1,000,000,1 ≤ L ≤ 100,A/B ≤ L 。
时间限制:1s1s
空间限制:128MB128MB
思路:
这道题目其实并没有难度,只需要根据题意,枚举 A A&#x27; B B’ 然后各种判断是否合法再求最小值即可。当然尽量不要用实数,精度把我卡下了一等线

题目上面还说A’和B‘是互质的,那么就说明她们的最大公因数必须是 1 1 才是一组合法的解。即必须满足 g c d ( a , b ) = 1 gcd(a,b)=1 ,这里采用辗转相除法(至于实现原理我没怎么理解,只是见多了会背了)
代码如下:

#include<bits/stdc++.h>
using namespace std;
int gcd(int a,int b)
{
	if (b==0) return a;
	else gcd(b,a%b);
}
int main()
{
	freopen("ratio.in","r",stdin);
	freopen("ratio.out","w",stdout);
	int ans1=0,ans2=0;
	int sta,stb,L;
	cin>>sta>>stb>>L;
	double min=100000000.0;
	double rat1=(sta*1.0)/(stb*1.0);
	for (int a=1;a<=L;++a)
	    for (int b=1;b<=L;++b)
	    {
	    	double rat2=(a*1.0)/(b*1.0);
	    	if ( !(rat2<rat1) && rat2-rat1<min && gcd(a,b)==1)
	    	{
	    		ans1=a;
	    		ans2=b;
	    		min=(rat2-rat1)*1.0;
			}
		}
	cout<<ans1<<' '<<ans2<<'\n';
	fclose(stdin);
	fclose(stdout);
	return 0;
}

T3:方阵

这道题如果要拿50分,就只需要暴力打表输出即可。否则就需要优化模拟或者用数学公式进行推到。
本题目的思路就是,
如果按暴力的方法去枚举,复杂度 O ( n ) O(n) ,然后这对于 n 30000 n≤30000 的数据量显然会 T L E TLE 从而只能拿到 50 50 分的部分分,我们需要对这种模拟方式进行优化.

原来,是模拟每一个数字,按照方阵的规律进行模拟.现在,我们可以去枚举每一圈的四个角落,然后当角落接近于这个角落的时候再进行暴搜.

emmmm.我们需要自行画图来理解哦(该方阵采用的例子是 n = 7 n=7

注意:此处需要读者自己再纸上画一个 n = 7 n=7 的方阵

我们把一个方阵分层,即:一圈为一层.

扫描二维码关注公众号,回复: 3652820 查看本文章

例如: 1 1 , 7 7 , 13 13 , 19 19 为一层,因为将他们四个点连起来就是一个圈;

同理, 25 25 , 29 29 , 33 33 , 37 37 同样也是一个圈.

然后我们枚举每一个圈或每一层,然后枚举每一个角落,(按照顺时针的顺序去枚举,即左上,右上,右下,左下)再进行判断:若这一个角落到下一个角落之间的位置如果存在所要求的点,那么再进行查找,否则转移到下面一个角落.

例如,当前枚举的行 s x = 1 , s y = 1 sx=1,sy=1 ,如果我们需要查找的点为 ( 1 , 3 ) (1,3) ,那么便直接用 f o r for 循环查找即可.若我们需要查找的点为 ( 2 , 4 ) (2,4) ,那么转移到下一个角落,至于如何转移,我们分4种情况.

1 1 :左上角:行数 + L +L

2 2 :右上角,列数 + L +L

3 3 :右下角,行数 L -L

4 4 :左下角,列数 L -L

至于L是多少?初始值是 n 1 n-1 ;然后每一圈都 2 -2 ,知道第 k k 圈为止.

k k 是方阵的圈数.

代码如下:

#include<bits/stdc++.h>
using namespace std;
int n,x,y,k;
inline int find1(int nowx,int nowy,int sum)
{
	for (int ny=nowy;;ny++,sum++)
		if (ny==y) return sum;
}
inline int find2(int nowx,int nowy,int sum)
{
	for (int nx=nowx;;nx++,sum++)
		if (nx==x) return sum;
}
inline int find3(int nowx,int nowy,int sum)
{
	for (int ny=nowy;;ny--,sum++)
		if (ny==y) return sum;
}
inline int find4(int nowx,int nowy,int sum)
{
	for (int nx=nowx;;nx--,sum++)
		if (nx==x) return sum;
}//以上find1234都是指找到范围后的暴力搜索 
inline int find()
{
	int L=n-1,num=1,sx=1,sy=1;//num表示方阵对应的数值 
	for (int i=1;i<=k;i++)
	{
		if (sx==x&&y<=sy+L) return find1(sx,sy,num);else sy+=L,num+=L;
		if (sy==y&&x<=sx+L) return find2(sx,sy,num);else sx+=L,num+=L;
		if (sx==x&&y>=sy-L) return find3(sx,sy,num);else sy-=L,num+=L;
		if (sy==y&&x>=sx-L) return find4(sx,sy,num);else sx=sx-L+1,sy++,num+=L;//四个角落查找 
		L-=2;
	}
	return 0;
}
int main()
{
	cin>>n>>x>>y;
	if (n%2) k=n/2+1;else k=n/2;
	cout<<find()<<endl;
	return 0;
} 

T4:

题目描述
给出如下定义:
1.子矩阵:从一个矩阵当中选取某些行和某些列交叉位置所组成的新矩阵(保持行与 列的相对顺序)被称为原矩阵的一个子矩阵。
例如,下面左图中选取第 2、4 行和第 2、4、5 列交叉位置的元素得到一个 2 * 3 的子矩阵如右图所示。
2.相邻的元素:矩阵中的某个元素与其上下左右四个元素(如果存在的话)是相邻的。
3.矩阵的分值:矩阵中每一对相邻元素之差的绝对值之和。
本题任务:给定一个 n 行 m 列的正整数矩阵,请你从这个矩阵中选出一个 r 行 c 列的子矩阵,使得这个子矩阵的分值最小,并输出这个分值。
输入格式
输入文件名为 submatrix.in
第一行包含用空格隔开的四个整数 n,m,r,c,意义如问题描述中所述,每两个整数之间用一个空格隔开。
接下来的 n 行,每行包含 m 个用空格隔开的整数,用来表示问题描述中那个 n 行 m 列的矩阵。
输出格式
输出文件名为 submatrix.out。
输出共 1 行,包含 1 个整数,表示满足题目描述的子矩阵的最小分值。
样例数据
input1
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
output1
6
input2
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
output2
16

我们先从暴力的思想去思考这一道问题,要求几条线交叉的点,那么必然:这一个点的横纵坐标必然是这几条线中的一个。由题意得,r和c是已知的,你只需要去枚举这些边,即r条横向的边,c条纵向的边即可。至于如何枚举————把每一个横向边的组合和纵向边的组合存储到一个数组里面,那么再四重循环暴力计算即可。显然这是不对的,大量dfs会消耗许多的时间复杂度。

因此,我们可以一般dfs,一半DP(听说纯DP要用状压写我不会)。用dfs去枚举每一条横向的边,在横向的边再做一遍动态规划即可。

边是如何枚举的这里不做论述,重点就在与后半部分的DP是如何做的:
我们需要预处理两个前缀和:sum1[i]表示第i列纵向绝对值之差;sum2[i][j]表示第i列到第j列的横向绝对值之差的和,其实一个子矩阵的分数也就是几个横向的分数和几个纵向的和。我们设f[i][j]为前i列以第j列结尾的最小分差,则f[i][j]为前i列以第j列结尾的最小值,那么我们去枚举与j相接的k,不难可得: f [ i ] [ j ] = m i n ( f [ i ] [ j ] , f [ i 1 ] [ k ] + s u m 1 [ j ] + s u m 2 [ k ] [ j ] ) ; f[i][j]=min(f[i][j],f[i-1][k]+sum1[j]+sum2[k][j]);

代码如下:

#include<bits/stdc++.h>
using namespace std;
int ans=1000000000;
int n,m,r,c;
int sum1[100];
int f[100][100];//f[i][j]表示已经选择了i列,以第j列结尾的最小分值 
int chs[10000];
int a[100][100];
int sum2[100][100];
void difficult_dp()
{
	memset(f,100,sizeof(f));
	memset(sum1,0,sizeof(sum1));
	memset(sum2,0,sizeof(sum2));
	for (int j=1;j<=m;++j)
	    for (int i=1;i<r;++i)
	        sum1[j]+=abs(a[chs[i]][j]-a[chs[i+1]][j]);
	for (int i=1;i<m;++i)
	    for (int j=i+1;j<=m;++j)
	        for (int k=1;k<=r;++k)
	            sum2[i][j]+=abs(a[chs[k]][i]-a[chs[k]][j]);
	f[0][0]=0;
	for (int i=1;i<=c;++i)
	    for (int j=i;j<=m;++j)
	        for (int k=0;k<j;++k)
	            f[i][j]=min(f[i][j],f[i-1][k]+sum1[j]+sum2[k][j]);
	for (int i=c;i<=m;++i) 
	    ans=min(ans,f[c][i]);
	return;
}
void dfs(int alr,int now)//当前状态为已经选择了alr行,选到了now行 
{
	if (alr==r) 
	{
		difficult_dp();
		return;
	}
	if (n-now<r-alr) return;
	for (int i=now+1;i<=n;++i)
	{
		chs[alr+1]=i;
		dfs(alr+1,i);
	}
	return;
}
int main()
{
	freopen("submatrix.in","r",stdin);
	freopen("submatrix.out","w",stdout);
	cin>>n>>m>>r>>c;
	for (int i=1;i<=n;++i)
	    for (int j=1;j<=m;++j)
	        cin>>a[i][j];
	dfs(0,0);
	cout<<ans<<'\n';
	fclose(stdin);
	fclose(stdout);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Ronaldo7_ZYB/article/details/82914705