NOIP2015普及组复赛

NOIP2015普及组复赛

整套题都出得不错,难度适中,层次分明

建议同学们在做题的时候还是先在草稿纸上分析,把关键算法的伪代码写出来,然后设计数据进行静态查错,没有问题后再到电脑上敲出代码。实际效率会高一些,也不容易出错,除非是你对这道题太熟悉了,不需要分析就可以过的可以例外(当然每个人还是根据自己的具体情况而定,只是建议尝试一下)。

第一题 金币

感觉这道题出得不错,普及组第一题就应该是这样的

简单模拟,有一个小判断就可以了

设计数据时要考虑边界,比如本题的1,10000,其他还应考虑能够手工验算的,比如7,5组数据验证一下应该就没问题了

#include<iostream>
using namespace std;
int main(){
	int k,n,d,s;
	n=d=s=0;
	cin>>k;
	do{
		n++;
		d+=n;
		if(d<=k) s+=n*n;//根据题目描述进行累加
		else s+=(k-(d-n))*n;//d>k说明最后一次不足n天,计算一下天数来乘n即可
	}while(d<k);
	cout<<s;
	return 0;
}

第二题 扫雷游戏

穷举整个雷区每一个元素周边的地雷情况就可以了

用dx,dy这样的增量数组写起来比较简洁,这个看每个人自己的习惯了

设计数据可以考虑全*,全?,边界值n,m为1的情况,100的情况需要设计随机数据代码来生成,有时间的话也可以考虑

#include<iostream>
#define N 105
using namespace std;
char a[N][N];
int b[N][N],dx[]={-1,1,0,0,-1,-1,1,1},dy[]={0,0,-1,1,-1,1,-1,1};
int main(){
	int i,j,k,n,m,x,y;
	cin>>n>>m;
	for(i=1;i<=n;i++)
		for(j=1;j<=m;j++)
			cin>>a[i][j];
	for(i=1;i<=n;i++)
		for(j=1;j<=m;j++)
			if(a[i][j]!='*')
				for(k=0;k<8;k++){
					x=i+dx[k];
					y=j+dy[k];
					if(x>0&&x<=n&&y>0&&y<=m&&a[x][y]=='*')
						b[i][j]++;
				}
	for(i=1;i<=n;i++){
		for(j=1;j<=m;j++)
			if(a[i][j]=='*')cout<<'*';
			else cout<<b[i][j];		
		cout<<endl;
	}
	return 0;
}

第三题 求和

这道题就有一定难度了,需要通过仔细的分析来找出规律,比较费时间

给出的数据也比较明确,也在引导你一步一步去进行深入思考

20分的数据,n<=100,和百钱百鸡相似,可以三重循环穷举

40分的数据,n<=3000,三重循环超时,可以只穷举x和z,用双重循环拿到40分,题目中也可以看到x和z的奇偶性必须是一致的,所以还可以优化一点儿

本题涉及到的数据比较大,所以在读数的时候考虑用scanf或者比scanf更优化的读入数据的方法,没准儿会多过一个点

#include<iostream>
#include<cstring>
#include<cstdio>
#define N 100005
#define M 10007
using namespace std;
int number[N],color[N];
int main(){
	int i,n,m,x,z;
	long long s=0;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
		scanf("%d",number+i);
	for(i=1;i<=n;i++)
		scanf("%d",color+i);
	for(x=1;x<n-1;x++)
		for(z=x+2;z<=n;z+=2)
			if(color[x]==color[z])
				s=(s+(x+z)*(number[x]+number[z]))%M;
	printf("%lld",s);
	return 0;
}

60分及100分的数据,n<=100000,双重循环超时,只有考虑O(n)或nlogn算法,

可以对某种颜色的奇数或偶数部分进行分析,找出其中规律

如果不能完全判定自己找出的规律完全正确,建议将40分的程序做个函数包,处理n<=3000的情况,同时可以作为验算找规律的代码正确性的依据。

用某种颜色在奇数或偶数情况下有4个数的情况下做分析,k个数也同理可证

x x1 x2 x3

color=1 有4个编号 x1 x2 x3 x4 ax1 ax2 ax3 ax4
(x1+x2)(ax1+ax2) (x1+x3)(ax1+ax3) (x1+x4)(ax1+ax4)
(x2+x3)(ax2+ax3) (x2+x4)(ax2+ax4)
(x3+x4)(ax3+ax4)
x1ax1+x1ax2+x1ax1+x1ax3+x1ax1+x1ax4
x1ax1(k-2) + x1*(ax1+ax2+ax3+ax4+axk)=x1*(ax1*(k-2)+(ax1+ax2+…+axk))
x2*(ax2*(k-2)+(ax1+ax2+…+axk)) … xk*(axk*(k-2)+(ax1+ax2+…+axk))

k个数的时候就是 x1*(ax1*(k-2)+(k个格子上数的和))+…+ xk(axk*(k-2)+(k个格子上数的和))

#include<iostream>
#include<cstring>
#include<cstdio>
#define N 100005
#define M 10007
using namespace std;
int a[N],c[N];
long long s[N][2],k[N][2],ans;
int main(){
	int i,n,m;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
		scanf("%d",a+i);
	for(i=1;i<=n;i++){
		scanf("%d",c+i);
		s[c[i]][i%2]++;//某种颜色下奇数、偶数的可以组成三元组的格子数量
		k[c[i]][i%2]=(k[c[i]][i%2]+a[i])%M;//累加该种情况下的格子上的数字和
	}	
	for(i=1;i<=n;i++)
		if(s[c[i]][i%2]>1)
			ans=(ans+i*(a[i]*(s[c[i]][i%2]-2)%M+k[c[i]][i%2]))%M;//列举计算结果
	printf("%lld",ans);
	return 0;
}

求模运算容易出错

需要设计结果超出10007的

第四题 推销员
阿明是一名推销员,他奉命到螺丝街推销他们公司的产品。

螺丝街是一条死胡同,出口与入口是同一个,街道的一侧是围墙,另一侧是住户。

螺丝街一共有N NN家住户,第i家住户到入口的距离为S i S_iS
i

米。

由于同一栋房子里可以有多家住户,所以可能有多家住户与入口的距离相等。

阿明会从入口进入,依次向螺丝街的X家住户推销产品,然后再原路走出去。

阿明每走1 11米就会积累1 11点疲劳值,向第i ii家住户推销产品会积累A i A_iA
i

点疲劳值。

阿明是工作狂,他想知道,对于不同的X XX,在不走多余的路的前提下,他最多可以积累多少点疲劳值。

输入格式
第一行有一个正整数N NN,表示螺丝街住户的数量。

接下来的一行有N NN个正整数,其中第i ii个整数S i S_iS
i

表示第i家住户到入口的距离。数据保证S 1 ≤ S 2 ≤ … ≤ S n < 1 0 8 S1≤S2≤…≤Sn<10^8S1≤S2≤…≤Sn<10
8

接下来的一行有N NN个正整数,其中第i ii个整数A i A_iA
i

表示向第i户住户推销产品会积累的疲劳值。数据保证A i < 1 0 3 A_i<10^3A
i

<10
3

输出格式
输出N行,每行一个正整数,第i行整数表示当X = i X=iX=i时,阿明最多积累的疲劳值。

数据范围
1 ≤ N ≤ 1 0 5 1≤N≤10^51≤N≤10
5

输入样例:

5
1 2 3 4 5
1 2 3 4 5

输出样例:

15
19
22
24
25

算法思想(贪心+前缀和)
输入样例分析:

当x = 1时,阿明最多积累的疲劳值是到第5家推销商品,ans = 5 + 2 * 5 = 15
当x = 2时,阿明最多积累的疲劳值是到第4、5家推销商品,ans = 4 + 5 + 2 * 5 = 19
当x = 3时,阿明最多积累的疲劳值是到第3、4、5家推销商品,ans = 3 + 4 + 5 + 2 * 5 = 22
当x = 4时,阿明最多积累的疲劳值是到第2、3、4、5家推销商品,ans = 2 + 3 + 4 + 5 + 2 * 5 = 24
当x = 5时,阿明最多积累的疲劳值是到第1、2、3、4、5家推销商品,ans = 1+ 2 + 3 + 4 + 5 + 2 * 5 = 25
通过分析样例可以发现,阿明向 x 家住户推销产品的能积累最大疲劳值只有两种情况:

推销给A i A_iA
i

值最大的x家
推销给A i A_iA
i

值最大的x - 1家,然后最后一家尽可能的远离得远
因此可以利用贪心思想,将所有住户按照A i A_iA
i

从大到小排序。然后,为了方便计算,可以利用前缀和的思想预处理出下列数组:

s[i]表示向前i家住户推销产品的疲劳值A i A_iA
i

之和
f[i]表示从入口到前i家住户距离S i S_iS
i

的最大值
g[i]表示从第i~n家2 × S i + A i 2\times S_i +A_i2×S
i

+A
i

的最大值,即第i家来回的距离 + 到第i推销商品疲劳值的最大值。
那么,向x家推销产品的最大疲劳值就是下面两种情况的最大值:

推销给A i A_iA
i

值最大的x家,即s[i] + 2 * f[i]
推销给A i A_iA
i

值最大的x - 1家,然后最后一家尽可能的远离得远,即s[i - 1] + g[i]
时间复杂度
预处理前缀和数组和求每个 x 对应的最大花费都是线性的,所以算法的瓶颈在于排序算法,时间复杂度为O ( n l o g n ) O(nlogn)O(nlogn)。

代码实现

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

typedef pair<int, int> PII;

const int N = 100010;

PII a[N];

int s[N]; //s[i]表示前i户Ai的和
int f[N]; //表示前i户Si的最大值
int g[N]; //表示i~n中2Si + Ai的最大值

int main()
{
    int n;
    scanf("%d", &n);
    
    //输入Si
    for(int i = 1; i <= n; i ++) scanf("%d", &a[i].second); 
    //输入Ai
    for(int i = 1; i <= n; i ++) scanf("%d", &a[i].first); 
    
    //按照Ai从大到小排序
    sort(a + 1, a + n + 1);
    reverse(a + 1, a + n + 1);
    
    //预处理s[i],表示前向前i家住户推销产品的疲劳值前缀和
    for(int i = 1; i <= n; i ++) s[i] = s[i - 1] + a[i].first;
    
    //预处理f[i],表示从入口到前i家住户距离Si的最大值
    for(int i = 1; i <= n; i ++) f[i] = max(f[i - 1], a[i].second);
    
    //预处理g[i],表示2 * Si + Ai的最大值
    //注意,这里从后向前计算, 先计算出小Ai值对应的g[i]
    //因为g[i]表示的是从i~n家2*Si +Ai最大值
    for(int i = n; i >= 1; i --) g[i] = max(g[i + 1], 2 * a[i].second + a[i].first);
    
    //输出
    for(int i = 1; i <= n; i ++)
    {
        printf("%d\n", max(s[i] + 2 * f[i], s[i - 1] + g[i]));
    }
    
    return 0;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值