2015年NOIP普及组T1-金币 [coin]
题目描述
国王将金币作为工资,发放给忠诚的骑士。第一天,骑士收到一枚金币;之后两天 (第二天和第三天),每天收到两枚金币;之后三天(第四、五、六天),每天收到三枚 金币;之后四天(第七、八、九、十天),每天收到四枚金币......;这种工资发放模式 会一直这样延续下去:当连续 N 天每天收到 N 枚金币后,骑士会在之后的连续 N+1 天 里,每天收到 N+1 枚金币。
请计算在前 K 天里,骑士一共获得了多少金币。
输入格式
输入文件名:coin.in
输入文件只有 1 行,包含一个正整数 K,表示发放金币的天数。(1 ≤ K ≤ 10,000)
输出格式
输出文件名:coin.out
输出文件只有 1 行,包含一个正整数,即骑士收到的金币数。
输入输出样例
输入样例1:
6
输出样例1:
14
输入样例2:
1000
输出样例2:
29820
说明
【输入输出样例 1 说明】
骑士第一天收到一枚金币;第二天和第三天,每天收到两枚金币;第四、五、六天, 每天收到三枚金币。因此一共收到 1+2+2+3+3+3=14 枚金币。
耗时限制1000ms 内存限制128MB
解析
考点:循环,模拟
参考代码
#include <bits/stdc++.h>
using namespace std;
int n,k = 1,c = 0;
int sum = 0;
int main(){
cin>>n;
for(int i = 1;i <= n;i++){
sum += k;
c++;
if(c == k){
k++;
c = 0;
}
}
cout<<sum;
return 0;
}
2015年NOIP普及组T2-扫雷游戏(mine)
题目描述
扫雷游戏是一款十分经典的单机小游戏。在n行m列的雷区中有一些格子含有地雷(称之为地雷格),其他格子不含地雷(称之为非地雷格)。玩家翻开一个非地雷格时,该格将会出现一个数字——提示周围格子中有多少个是地雷格。游戏的目标是在不翻出任何地雷格的条件下,找出所有的非地雷格。
现在给出n行m列的雷区中的地雷分布,要求计算出每个非地雷格周围的地雷格数。
注:一个格子的周围格子包括其上、下、左、右、左上、右上、左下、右下八个方向上与之直接相邻的格子。
输入格式
第一行是用一个空格隔开的两个整数n和m,分别表示雷区的行数和列数。
接下来n行,每行m个字符,描述了雷区中的地雷分布情况。字符’*’表示相应格子是地雷格,字符’?’表示相应格子是非地雷格。
输出格式
输出文件包含n行,每行m个字符,描述整个雷区。用’*’表示地雷格,用周围的地雷个数表示非地雷格。相邻字符之间无分隔符。
样例
输入#1
3 3
*??
???
?*?
输出#1
*10
221
1*1
输入#2
2 3
?*?
*??
输出#2
2*1
*21
数据范围
对于 100%的数据,1≤n≤100,1≤m≤100。
耗时限制1000ms 内存限制128MB
解析
考点:模拟,二维数组
思路:逐个点判断,如果是非地雷格,求出该点八方向有几个地雷。
参考代码
#include <bits/stdc++.h>
using namespace std;
char a[110][110];
int n,m;
int main(){
cin>>n>>m;
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
cin>>a[i][j];
}
}
//计算每个非地雷格八方向中有几个地雷
int c;
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
if(a[i][j] == '*') cout<<'*';
else{
//计算 8 方向的地雷的数量
c = 0;
if(a[i-1][j-1]=='*') c++;
if(a[i-1][j]=='*') c++;
if(a[i-1][j+1]=='*') c++;
if(a[i][j+1]=='*') c++;
if(a[i+1][j+1]=='*') c++;
if(a[i+1][j]=='*') c++;
if(a[i+1][j-1]=='*') c++;
if(a[i][j-1]=='*') c++;
cout<<c;
}
}
cout<<endl;
}
return 0;
}
2015年NOIP普及组T3- 求和 [sum]
题目描述
一条狭长的纸带被均匀划分出了n个格子,格子编号从1到n。每个格子上都染了一种颜色color_i用[1,m]当中的一个整数表示),并且写了一个数字number_i。
定义一种特殊的三元组:(x,y,z),其中x,y,z都代表纸带上格子的编号,这里的三元组要求满足以下两个条件:
1、xyz是整数,x<y<z,y-x=z-y
2、colorx=colorz
满足上述条件的三元组的分数规定为(x + z) * (number_x + number_z)。整个纸带的分数规定为所有满足条件的三元组的分数的和。这个分数可能会很大,你只要输出整个纸带的分数除以 10007 所得的余数即可。
输入格式
输入文件名为 sum.in。
第一行是用一个空格隔开的两个正整数n和m,n表纸带上格子的个数,m表纸带上颜色的种类数。
第二行有n用空格隔开的正整数,第i数字number表纸带上编号为i格子上面写的数字。
第三行有n用空格隔开的正整数,第i数字color表纸带上编号为i格子染的颜色。
输出格式
输出文件名为 sum.out。
共一行,一个整数,表示所求的纸带分数除以 10,007 所得的余数。
输入输出样例
输入样例1:
6 2 5 5 3 2 2 2 2 2 1 1 2 1
输出样例1:
82
输入样例2:
15 4 5 10 8 2 2 2 9 9 7 7 5 6 4 2 4 2 2 3 3 4 3 3 2 4 4 4 4 1 1 1
输出样例2:
1388
说明
【输入输出样例 1 说明】
纸带如题目描述中的图所示。
所有满足条件的三元组为: (1, 3, 5), (4, 5, 6)。
所以纸带的分数为(1 + 5)*(5 + 2) + (4 + 6)*(2 + 2) = 42 + 40 = 82。
【数据说明】
对于第 1 组至第 2 组数据, 1 ≤ n ≤ 100, 1 ≤ m ≤ 5;
对于第 3 组至第 4 组数据, 1 ≤ n ≤ 3000, 1 ≤ m ≤ 100;
对于第 5 组至第 6 组数据, 1 ≤ n ≤ 100000, 1 ≤ m ≤ 100000,且不存在出现次数超过 20 的颜色;
对 于 全 部 10 组 数 据 , 1 ≤ n ≤ 100000, 1 ≤ m ≤ 100000, 1 ≤ color_i ≤ m,1≤number_i≤100000
耗时限制1000ms 内存限制128MB
解析:
考点:数学推导
参考代码:
#include <bits/stdc++.h>
using namespace std;
/*
三元组:格子编号是等差数列
第一格和第三格颜色相同
(x+z)*(number_x+number_z)
*/
int num[100100],color[100100];
int s,i,j,n,m;
int main(){
cin>>n>>m;//m:颜色种类
for(i = 1;i <= n;i++){
cin>>num[i];
}
for(i = 1;i <= n;i++){
cin>>color[i];
}
//颜色相同,奇偶性相同的格子,就满足条件,可以计算
//遍历所有可能的格子编号为等差数列的格子
//遍历第 1 个格子
for(i = 1;i < n;i++){
//遍历第 3 个格子
for(j = i + 2;j <= n;j = j + 2){
if(color[i] == color[j]){
s = s + (i + j) * (num[i] + num[j]);
s = s % 10007;
}
}
}
cout<<s;
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10,MOD = 10007;
int num[N],color[N];
//将所有的数据按照颜色分组、再按下标的奇偶性分组
//求对应分组的数有几个、对应分组的数值和是多少
int cnt[N][2],sum[N][2];
int n,m,res = 0;
int main(){
scanf("%d%d",&n,&m);
//读入 n 个数的数值
for(int i = 1;i <= n;i++) scanf("%d",&num[i]);
//读入颜色
for(int i = 1;i <= n;i++){
scanf("%d",&color[i]);
//分组统计
cnt[color[i]][i%2]++;
sum[color[i]][i%2]=(sum[color[i]][i%2]+num[i])%MOD;
}
//计算
for(int i = 1;i <= n;i++){
res = (res + i * (sum[color[i]][i%2]+(cnt[color[i]][i%2]-2)*num[i] % MOD)) % MOD;
}
printf("%d",res);
return 0;
}
另一种写法:
#include <bits/stdc++.h>
using namespace std;
int n, m;
int num[100005], color[100005];
int a1[100005][2], a2[100005][2], a3[100005][2], a4[100005][2];
const int p = 10007;
int main() {
scanf("%d%d", &n, &m);
for(int i = 1;i <= n; i++) {
scanf("%d", &num[i]);
}
for(int i = 1;i <= n; i++) {
scanf("%d", &color[i]);
}
int ans = 0;
for(int i = 1;i <= n; i++) {
int y = i % 2;
ans += a1[color[i]][y]; ans %= p;
ans += a2[color[i]][y] * num[i]; ans %= p;
ans += a3[color[i]][y] * i; ans %= p;
ans += a4[color[i]][y] * i % p * num[i] % p; ans %= p;
a1[color[i]][y] += 1ll * i * num[i] % p;
a2[color[i]][y] += i; a2[color[i]][y] %= p;
a3[color[i]][y] += num[i]; a3[color[i]][y] %= p;
++a4[color[i]][y]; a4[color[i]][y] %= p;
}
printf("%d", ans);
}
2015年NOIP普及组T4- 推销员
题目描述
阿明是一名推销员,他奉命到螺丝街推销他们公司的产品。螺丝街是一条死胡同,出口与入口是同一个,街道的一侧是围墙,另一侧是住户。螺丝街一共有N家住户,第i家住户到入口的距离为Si米。由于同一栋房子里可以有多家住户,所以可能有多家住户与入口的距离相等。阿明会从入口进入,依次向螺丝街的X家住户推销产品,然后再原路走出去。
阿明每走1米就会积累1点疲劳值,向第i家住户推销产品会积累Ai点疲劳值。阿明是工作狂,他想知道,对于不同的X,在不走多余的路的前提下,他最多可以积累多少点疲劳值。
输入格式
第一行有一个正整数N,表示螺丝街住户的数量。
接下来的一行有N个正整数,其中第i个整数Si表示第i家住户到入口的距离。数据保证S1≤S2≤…≤Sn<10^8。
接下来的一行有N个正整数,其中第i个整数Ai表示向第i户住户推销产品会积累的疲劳值。数据保证Ai<10^3。
输出格式
输出N行,每行一个正整数,第i行整数表示当X=i时,阿明最多积累的疲劳值。
输入输出样例
输入样例1:
5 1 2 3 4 5 1 2 3 4 5
输出样例1:
15 19 22 24 25
输入样例2:
5 1 2 2 4 5 5 4 3 4 1
输出样例2:
12 17 21 24 27
说明
【输入输出样例1说明】
X=1:向住户5推销,往返走路的疲劳值为5+5,推销的疲劳值为5,总疲劳值为15。
X=2:向住户4、5推销,往返走路的疲劳值为5+5,推销的疲劳值为4+5,总疲劳值为5+5+4+5=19。
X=3:向住户3、4、5推销,往返走路的疲劳值为5+5,推销的疲劳值3+4+5,总疲劳值为5+5+3+4+5=22。
X=4:向住户2、3、4、5推销,往返走路的疲劳值为5+5,推销的疲劳值2+3+4+5,总疲劳值5+5+2+3+4+5=24。
X=5:向住户1、2、3、4、5推销,往返走路的疲劳值为5+5,推销的疲劳值1+2+3+4+5,总疲劳值5+5+1+2+3+4+5=25。
【输入输出样例2说明】
X=1:向住户4推销,往返走路的疲劳值为4+4,推销的疲劳值为4,总疲劳值4+4+4=12。
X=2:向住户1、4推销,往返走路的疲劳值为4+4,推销的疲劳值为5+4,总疲劳值4+4+5+4=17。
X=3:向住户1、2、4推销,往返走路的疲劳值为4+4,推销的疲劳值为5+4+4,总疲劳值4+4+5+4+4=21。
X=4:向住户1、2、3、4推销,往返走路的疲劳值为4+4,推销的疲劳值为5+4+3+4,总疲劳值4+4+5+4+3+4=24。或者向住户1、2、4、5推销,往返走路的疲劳值为5+5,推销的疲劳值为5+4+4+1,总疲劳值5+5+5+4+4+1=24。
X=5:向住户1、2、3、4、5推销,往返走路的疲劳值为5+5,推销的疲劳值为5+4+3+4+1,
总疲劳值5+5+5+4+3+4+1=27。
【数据说明】
对于20%的数据,1≤N≤20;
对于40%的数据,1≤N≤100;
对于60%的数据,1≤N≤1000;
对于100%的数据,1≤N≤100000。
解析:
考点:贪心、前缀最大值、前缀和,优先队列,单调队列
解法一:贪心,前缀最大值,前缀和
分析:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
struct node{
int s,a;
}num[N];
int fa[N];//ai 的前缀和
int fs[N];//si 的前缀最大值
int fr[N];//从 i~n 之间 2*si+ai 的最大值
int n;
//排序规则:按推销疲劳值降序排序
bool cmp(node n1,node n2){
return n1.a > n2.a;
}
int main() {
scanf("%d",&n);
for(int i = 1;i <= n;i++) scanf("%d",&num[i].s);
for(int i = 1;i <= n;i++) scanf("%d",&num[i].a);
//将所有的点按照推销疲劳值降序排序
sort(num+1,num+n+1,cmp);
//ai 的前缀和
for(int i = 1;i <= n;i++) fa[i] = fa[i-1] + num[i].a;
//si 的前缀最大值
for(int i = 1;i <= n;i++) fs[i] = max(fs[i-1],num[i].s);
//从 i~n 之间 2*si+ai 的最大值
for(int i = n;i >= 1;i--) fr[i] = max(fr[i+1],2*num[i].s+num[i].a);
//计算
for(int i = 1;i <= n;i++){
printf("%d\n",max(fs[i]*2+fa[i],fa[i-1]+fr[i]));
}
return 0;
}
解法二:优先队列和单调队列
思考
考虑x右边的两个住户i和j,假设选择x前,向i住户推销的总疲劳值>向j住户推销的总疲劳值,那么选x后呢?
①向i住户推销的总疲劳值>向j住户推销的疲劳值,即:s[i]*2+a[i]>s[j]*2+a[j]。
②选择x后,i和的推销疲劳值变为:(s[i]-s[x])*2+a[i]和(s[j]-s[x])*2+a[j]。
③由于向i和j推销的总疲劳值都减少了s[x]*2;故i和j的推销疲劳值大小关系不变。
将住户的总推销疲劳值按照从大到小排序,x选择前后其右边住户的疲劳值大小关系不变。
右边最大值:从队首依次将位置≤x的元素删除(过期),删除后的队首元素即是右边最大值。
参考代码:
#include <iostream>
#include<queue>
#include<algorithm> //优先队列维护下标
using namespace std;
const int N=100005;
struct Node{
int s,a,p; //距离、疲劳值、位置
} node[N];
//按照疲劳值从大到小排序
bool cmp(const Node& x,const Node& y){
return x.s*2+x.a>y.s*2+y.a;
}
int n,now,ans,s[N],a[N];
priority_queue<int> lq; //左边优先队列(疲劳值)
queue<int> rq; //右边单调队列
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>node[i].s;
node[i].p=i;
s[i]=node[i].s;
}
for(int i=1;i<=n;i++){
cin>>node[i].a;
a[i]=node[i].a;
}
sort(node+1,node+1+n,cmp);
//队列中存储元素的位置,即下标
for(int i=1;i<=n;i++) rq.push(node[i].p); //排序后的数据加入单调递减序列(结构体)
/*
时间复杂度:此处虽然有循环嵌套,但实际上执行次数还是o(n)级别
1.删除过期元素,最多执行n次,即把右边队列中的元素一个一个出队o(n)
2.将过期元素放入左边队列,最多就是把所有元素全部放入优先队列,也是n次o(n)
*/
for(int i=1;i<=n;i++){ //每次选一家最大的,共n次
int mx=0,pre=now;
if(!lq.empty()) mx=lq.top(); //找出now左边最大的
//删除过期元素(已经属于左半边部分的元素和上一次最大的元素(右边))
while(!rq.empty()&&rq.front()<=now) rq.pop();
//比较左右两边最值 ,更新now
if(!rq.empty()){ //右边最值
mx=max(mx,(s[rq.front()]-s[now])*2+a[rq.front()]); //右边最大的和左边比较
}
if(!lq.empty()&&mx==lq.top()) lq.pop(); //左边大
else{ //右边大
now=rq.front(); //更新为右边较大者位置
//rq.pop(); //删除已经使用的元素
for(int i=pre+1;i<now;i++) lq.push(a[i]) ; //now左边入左边优先队列
}
ans+=mx;
cout<<ans<<endl;
}
return 0;
}