2018年NOIP普及组复赛真题解析

2018年NOIP普及组T1-标题统计

题目描述

凯凯刚写了一篇美妙的作文,请问这篇作文的标题中有多少个字符? 注意:标题中可能包含大、小写英文字母、数字字符、空格和换行符。统计标题字 符数时,空格和换行符不计算在内。

输入格式

输入文件名为title.in

输入文件只有一行,一个字符串S

输出格式

输出文件名为title.out

输出文件只有一行,包含一个整数,即作文标题的字符数(不含空格和换行符)。

输入输出样例

输入样例1:

234

输出样例1:

3

输入样例2:

Ca 45

输出样例2:

4

说明

【输入输出样例 1 说明】

标题中共有 3 个字符,这 3 个字符都是数字字符。

【输入输出样例 2 说明】 标题中共有5个字符,包括1个大写英文字母, 1个小写英文字母和 2个数字字符, 还有1个空格。由于空格不计入结果中,故标题的有效字符数为4个。

【数据规模与约定】

规定 |s| 表示字符串 s 的长度(即字符串中的字符和空格数)。

对于 40% 的数据,1 ≤ |s| ≤ 5,保证输入为数字字符及行末换行符。

对于 80% 的数据,1 ≤ |s| ≤ 5,输入只可能包含大、小写英文字母、数字字符及行末换行符。

对于 100% 的数据,1 ≤ |s| ≤ 5,输入可能包含大、小写英文字母、数字字符、空格和行末换行符。

耗时限制1000ms  内存限制256MB

解析:

考点:字符串、字符

参考代码

#include <bits/stdc++.h>
using namespace std;
string s;
int ans = 0;
int main(){
 	getline(cin,s);
 	for(int i = 0;i < s.size();i++){
 		if(s[i] != ' '){
 			ans++;
 		}
 	}	
 	cout<<ans;
	return 0;
}

2018年NOIP普及组T2-龙虎斗

题目描述

轩轩和凯凯正在玩一款叫《龙虎斗》的游戏,游戏的棋盘是一条线段,线段上有n个兵营(自左至右编号 1~n),相邻编号的兵营之间相隔1厘米,即棋盘为长度为n−1 厘米的线段。i号兵营里有ci位工兵。下图1为n=6的示例:

轩轩在左侧,代表“龙”;凯凯在右侧,代表“虎”。 他们以m号兵营作为分界, 靠左的工兵属于龙势力,靠右的工兵属于虎势力,而第m号兵营中的工兵很纠结,他们不属于任何一方。

一个兵营的气势为:该兵营中的工兵数×该兵营到m号兵营的距离;参与游戏一方的势力定义为:属于这一方所有兵营的气势之和。

下图2为n=6, m=4的示例,其中红色为龙方,黄色为虎方:

游戏过程中,某一刻天降神兵,共有s1位工兵突然出现在了p1号兵营。作为轩轩和凯凯的朋友,你知道如果龙虎双方气势差距太悬殊,轩轩和凯凯就不愿意继续玩下去了。为了让游戏继续,你需要选择一个兵营p2,并将你手里的s2位工兵全部派往兵营p2,使得双方气势差距尽可能小。

注意:你手中的工兵落在哪个兵营,就和该兵营中其他工兵有相同的势力归属(如果落在m号兵营,则不属于任何势力)。

输入格式

输入的第一行包含一个正整数n,代表兵营的数量。

接下来的一行包含n个正整数,相邻两数之间以一个空格分隔,第i个正整数代表编号为i的兵营中起始时的工兵数量ci。 

接下来的一行包含四个正整数,相邻两数间以一个空格分隔,分别代表m,p1,s1,s2。

输出格式

输出一行,包含一个正整数,即p2,表示你选择的兵营编号。如果存在多个编号同时满足最优,取最小的编号。

输入输出样例

输入样例1:
6  
2 3 2 3 2 3  
4 6 5 2
输出样例1:
2
输入样例2:
6 
1 1 1 1 1 16  
5 4 1 1
输出样例2:
1

说明

【样例1说明】

双方以m=4号兵营分界,有s1=5位工兵突然出现在p1=6号兵营。

龙方的气势为:

2 × (4 − 1) + 3 × (4 − 2) + 2 × (4 − 3) = 14 

虎方的气势为:

2 × (5 − 4) + (3 + 5) × (6 − 4) = 18

当你将手中的s2=2位工兵派往p2=2号兵营时,龙方的气势变为:

14 + 2 × (4 − 2) = 18

此时双方气势相等。

【样例2说明】

双方以m=5号兵营分界,有s1=1位工兵突然出现在p1=4号兵营。

龙方的气势为:

1 × (5 − 1) + 1 × (5 − 2) + 1 × (5 − 3) + (1 + 1) × (5 − 4) = 11

虎方的气势为:

16 × (6 − 5) = 16

当你将手中的s2=1位工兵派往p2=1号兵营时,龙方的气势变为:

11 + 1 × (5 − 1) = 15

此时可以使双方气势的差距最小。

【数据规模与约定】

1<m<n,1≤p1≤n。

对于 20% 的数据,n= 3, m= 2, ci=1, s1,s2 ≤ 100。 

另有 20% 的数据,n≤ 10, p1 = m, ci = 1, s1, s2 ≤ 100。 

对于 60% 的数据,n ≤ 100, ci = 1, s1, s2 ≤ 100。

对于 80% 的数据,n ≤ 100, ci,s1,s2 ≤ 100。

对于 100% 的数据,n ≤ 10^5, ci, s1, s2 ≤ 10^9。

耗时限制1000ms  内存限制256MB

解析

考点::枚举、模拟

思路:

1. 兵营的气势为:该兵营中的工兵数 × 该兵营到 m 号兵营的距离
2.s1 位工兵突然出现在了 p1 号兵营, 选择一个兵营 p2 ,并将你手里的 s2 位工兵全部派往兵营 p2
使得双方气势差距尽可能小。
思路:
1. 计算每一方的起始值
2. 打擂台找出将 s2 个人放到哪个兵营,使得最终的差值更小

参考代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL a[N],n,m,p1,s1,s2,r,r1 = 0,r2 = 0,mi;
int main(){
	 scanf("%d",&n);
	 for(int i = 1;i <= n;i++){
	 	scanf("%d",&a[i]);
	 }
	 scanf("%d%d%d%d",&m,&p1,&s1,&s2);
	 //天降神兵
	 a[p1] += s1;
	 //计算两方的势力和
	 for(int i = 1;i <= n;i++){
		 if(i < m){
		 r1 += (m - i) * a[i];
		 }else if(i > m){
		 r2 += (i - m) * a[i];
		 }
	 }
	 //计算将 s2 个人投入哪个兵营可以使得差值更小
	 mi = abs(r1 - r2);
	 r = m;//有可能将 s2 个人投入任何一个兵营都会使得差值更大
	 for(int i = 1;i <= n;i++){
		 if(i < m){
			if(abs(r1+(m-i)*s2-r2)<mi){
				 mi = abs(r1+(m-i)*s2-r2);
				 r = i;
			 }
		 }else if(i > m){
			if(abs(r2+(i-m)*s2-r1)<mi){
				 mi = abs(r2+(i-m)*s2-r1);
				 r = i;
		 	}
		 }
	 }
	 cout<<r;
	return 0;
}

2018年NOIP普及组T3-摆渡车

题目描述

有n名同学要乘坐摆渡车从人大附中前往人民大学,第i位同学在第ti分钟去等车。只有一辆摆渡车在工作,但摆渡车容量可以视为无限大。

摆渡车从人大附中出发、 把车上的同学送到人民大学、再回到人大附中(去接其他同学),这样往返一趟总共花费m分钟(同学上下车时间忽略不计)。摆渡车要将所有同学都送到人民大学。

凯凯很好奇,如果他能任意安排摆渡车出发的时间,那么这些同学的等车时间之和最小为多少呢?

注意:摆渡车回到人大附中后可以即刻出发。

输入格式

输入文件名为 bus.in。

第一行包含两个正整数n,m,以一个空格分开,分别代表等车人数和摆渡车往返一趟的时间。

第二行包含n个正整数,相邻两数之间以一个空格分隔,第i个非负整数ti代表第i个同学到达车站的时刻。

输出格式

输出文件名为 bus.out。 

输出一行,一个整数,表示所有同学等车时间之和的最小值(单位:分钟)。

输入输出样例

输入样例1:
5 1  3 4 4 3 5
输出样例1:
0
输入样例2:
5 5 11 13 1 5 5
输出样例2:
4

说明

【输入输出样例 1 说明】

同学 1 和同学 4 在第 3 分钟开始等车,等待 0 分钟,在第 3 分钟乘坐摆渡车 出发。摆渡车在第 4 分钟回到人大附中。

同学 2 和同学 3 在第 4 分钟开始等车,等待 0 分钟,在第 4 分钟乘坐摆渡车 出发。摆渡车在第 5 分钟回到人大附中。

同学 5 在第 5 分钟开始等车,等待 0 分钟,在第 5 分钟乘坐摆渡车出发。自此 所有同学都被送到人民大学。总等待时间为 0。

【输入输出样例 2 说明】

同学 3 在第 1 分钟开始等车,等待 0 分钟,在第 1 分钟乘坐摆渡车出发。摆渡车在第 6 分钟回到人大附中。

同学 4 和同学 5 在第 5 分钟开始等车,等待 1 分钟,在第 6 分钟乘坐摆渡车出发。摆渡车在第 11 分钟回到人大附中。

同学 1 在第 11 分钟开始等车,等待 2 分钟;同学 2 在第 13 分钟开始等车,等待 0 分钟。他/她们在第 13 分钟乘坐摆渡车出发。自此所有同学都被送到人民大学。 

总等待时间为4。可以证明,没有总等待时间小于 4 的方案。

【数据规模与约定】

对于 10% 的数据,n≤10, m=1, 0≤ti ≤100。

对于 30% 的数据,n≤20, m≤2, 0≤ti ≤100。 

对于 50%的数据,n≤500, m≤100, 0≤ti≤10^4。 

另有 20%的数据,n≤500, m≤10, 0≤ti≤4×10^6。 

对于 100% 的数据,n≤500, m≤100, 0≤ti≤4×10^6。

耗时限制1000ms  内存限制256MB

解析

考点:动态规划,DP优化,记忆化搜索

解法1:

1 、分析:有 10% 的数据是送分的, m=1 ,大家都不需要等待,直接输出 0
2 、设 f[i] 代表:在第 i 个时间点发车,第 i 个时间点之前的同学等待时间的最小和。

f[i] = f[j] + (i - t1) * x1 + (i - t2) * x2 + (i - t3) * x3

= f[j] + i * (x1 + x2 + x3) - (t1 * x1 + t2 * x2 + t3 * x3)

也就是:

f[i] = f[j] + i * 区间中人数和 - 区间中所有数的和。

f[i] = i * cnt[i]  sum[i];//边界条件

f[i] = min(f[i],f[j] + i * (cnt[i]-cnt[j])  (sum[i]-sum[j]));

3、由于两班车之间的间隔是 m,因此 j 的取值范围是: (i-2*m,i-m]

        i 的取值范围是: [1,max(ti)+m)

        答案所在的区间:[max(ti),max(ti)+m)

        由于 n≤500m≤100ti≤4 * 10^6

        按上述方法,时间超限。

4、优化点:

1)如果在第 i 分钟发车,但是从 i-m  i 分钟内,没有人, 那么是不需要计算的,直 接获取前面算出来的最少等车时间即可: f[i] = f[i-m]

2)可以利用前缀和求区间和。

举例:

假设读入: 1 1 3 5 5 6

可以用 cnt 数组,存储人数的前缀和,用 sum 数组存储数据和的前缀和。 先计算出每个点的人数, 和每个点的数据和。

cnt 数组:

再算前缀和即可。

参考代码

#include <bits/stdc++.h>
using namespace std;
const int N = 4e6 + 110;
//如果在时间点 i 发车,之前同学等待的最小时间和
int f[N];
//cnt:时间点 i 等车的人数
//sum:时间点 i 等车的数据和
int cnt[N],sum[N];
int n,m,ma;
int main(){
	 scanf("%d%d",&n,&m);
	 int t;
	 for(int i = 1;i <= n;i++){
		 scanf("%d",&t);
		 cnt[t]++;
		 sum[t] += t;
		 ma = max(ma,t);
	 }
	 //循环可能的发车时间
	 for(int i = 1;i < ma + m;i++){
		 //计算前缀和
		 cnt[i] += cnt[i-1];
		 sum[i] += sum[i-1];
		 //如果在 i-m 到 i 之间没有人等车
		 //状态值可以继承前面算好的值
		 if(i>=m&&cnt[i]-cnt[i-m]==0){
			 f[i] = f[i-m];
			 continue;
		 }
		 //讨论前一次发车可能的时间范围
		 f[i] = i * cnt[i] - sum[i];//边界条件
		 int s = i-2*m+1;
		 if(s < 0) s = 0;
		 for(int j=s;j<=i-m;j++){
		 	f[i] = min(f[i],f[j]+i*(cnt[i]-cnt[j])-(sum[i]-sum[j]));
		 }
	 }
	 //最终结果在[ma,ma+m)求最小的 f[i]
	 int ans = INT_MAX;
	 for(int i = ma;i < ma + m;i++){
	 	ans = min(ans,f[i]);
	 }
	 cout<<ans;
	 return 0;
}

写法二:记忆化搜索

参考代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cctype>
using namespace std;
int n,m,t[505],mem[505][505];
//因为0<=st-t[i]<=m,因此可以记忆化,把这个作为状态的第二维 

int solve(int i,int st){//记忆化搜索。i:第i个人,st:开始时间st 
    if (i==n+1)//所有人都上车了 
        return 0;
    if (st<t[i])//如果现在的时间没有人,就到下一个人的到达时间 
        return solve(i,t[i]);
    if (mem[i][st-t[i]])//记忆化 
        return mem[i][st-t[i]];
    int sum=0,j=i;
    //车等人 
    while (j<=n && t[j]<=st)
        sum+=t[j++];
    int best=st*(j-i)-sum+solve(j,st+m); 
    //人等车
    for (;j<=n;j++){    
        sum+=t[j];
        best=min(t[j]*(j-i+1)-sum+solve(j+1,t[j]+m),best);
    }
    return mem[i][st-t[i]]=best;
}

int main(){
    cin >> n >> m;
    for (int i=1;i<=n;i++)
        cin >> t[i];
    sort(t+1,t+n+1);//显然从小到大按照时间排序更好算 
    cout << solve(1,0) << endl;
    return 0;
}

2018年NOIP普及组T4-对称二叉树

题目描述

一棵有点权的有根树如果满足以下条件,则被轩轩称为对称二叉树:

1. 二叉树;

2. 将这棵树所有节点的左右子树交换,新树和原树对应位置的结构相同且点权相等。

下图中节点内的数字为权值,节点外的id表示节点编号。

现在给出一棵二叉树,希望你找出它的一棵子树,该子树为对称二叉树,且节点数最多。请输出这棵子树的节点数。

注意:只有树根的树也是对称二叉树。本题中约定,以节点T为子树根的一棵“子 树”指的是:节点T和它的全部后代节点构成的二叉树。

输入格式

输入文件名为 tree.in。

第一行一个正整数n,表示给定的树的节点的数目,规定节点编号1~n,其中节点1是树根。

第二行n个正整数,用一个空格分隔,第i个正整数vi代表节点i的权值。

接下来n行,每行两个正整数li,ri分别表示节点i的左右孩子的编号。如果不存在左/右孩子,则以−1表示。两个数之间用一个空格隔开。

输出格式

【输出格式】

输出文件名为 tree.out。 

输出文件共一行,包含一个整数,表示给定的树的最大对称二叉子树的节点数。

输入输出样例

输入样例1:
2 1 3 2 -1  -1 -1
输出样例1:
1
输入样例2:
10  2 2 5 5 5 5 4 4 2 3  9 10 -1 -1 -1 -1 -1 -1 -1 -1 -1 2 3 4 5 6 -1 -1 7 8
输出样例2:
3

说明

【输入输出样例 1 说明】

最大的对称二叉子树为以节点 2 为树根的子树,节点数为 1。

【输入输出样例 2 说明】

最大的对称二叉子树为以节点 7 为树根的子树,节点数为 3。

【数据规模与约定】 

共 25 个测试点。 vi≤ 1000。

测试点 1~3,n≤ 10,保证根结点的左子树的所有节点都没有右孩子,根结点的右子树的所有节点都没有左孩子。

测试点 4~8,n≤ 10。

测试点 9~12,n≤ 10^5,保证输入是一棵“满二叉树”。 

测试点 13~16,n ≤ 10^5,保证输入是一棵“完全二叉树”。 

测试点 17~20,n ≤ 10^5,保证输入的树的点权均为1。 

测试点 21~25,n ≤ 10^6。

本题约定: 

层次:节点的层次从根开始定义起,根为第一层,根的孩子为第二层。树中任一节点的层次等于其父亲节点的层次加 1。 

树的深度:树中节点的最大层次称为树的深度。

满二叉树:设二叉树的深度为h,且二叉树有2^h−1个节点,这就是满二叉树。

完全二叉树:设二叉树的深度为h,除第h层外,其它各层的结点数都达到最大个数,第h层所有的结点都连续集中在最左边,这就是完全二叉树。

耗时限制1000ms  内存限制256MB

解析

考点:二叉树,树的遍历

思路:

1.以二叉树的每一个节点为根节点, 寻找对称二叉树。

2.将找到的二叉树统计节点数。

3.比较最大的节点数。

对于每一次检验是否是对称二叉树的操作,当二叉树为完全二叉树的时候, 时间复杂度最 大,  log2n n 次操作, 因此时间复杂度为 n * log2n

只有树根的树也是对称二叉树。
思路:
1. 先求出每个结点对应的子树大小
2. 从每个结点开始,判断每个结点对应的子树是否对称
如果对称,打擂台求最大对称子树大小
参考代码:
#include<bits/stdc++.h>
using namespace std; 
const int N = 1e6 + 10;
struct node{
	int lc,rc,v;//v 代表权值
}a[N];
//num[]:存储每个结点对应的子树大小
int n,num[N];
//求每个结点对应的子树大小
int getNum(int x){
	num[x] = 1;
	if(a[x].lc != -1) num[x] += getNum(a[x].lc);
	if(a[x].rc != -1) num[x] += getNum(a[x].rc);
	return num[x];
}
//深搜判断 x 结点和 y 结点对应的子树是否对称
bool dfs(int x,int y){
	if(num[x]!=num[y]) return false;//子树大小不一致
	if(x==-1&&y==-1) return true;//两个子结点都不存在
	if(x==-1||y==-1) return false;//一个有子, 一一个没有
	if(a[x].v!=a[y].v) return false;//权值不同
	return dfs(a[x].lc,a[y].rc) & dfs(a[x].rc,a[y].lc);
}
int main(){
	scanf("%d",&n);
	//读入每个点的权值
	for(int i = 1;i <= n;i++){
		scanf("%d",&a[i].v);
	}
	//读入每个结点子结点的编号
	for(int i = 1;i <= n;i++){
		scanf("%d%d",&a[i].lc,&a[i].rc);
	}
	//求出每个结点对应的子树大小
	getNum(1);
	//遍历每个结点,逐个判断该结点对应的子树是否对称
	int ans = 1;
	for(int i = 1;i <= n;i++){
		if(dfs(a[i].lc,a[i].rc)){
			ans = max(ans,num[i]);
		}
	}
	cout<<ans;
	return 0;
}	

写法二:

#include<bits/stdc++.h>
using namespace std;

const int N = 1e6+5;
int n, ans, v[N], l[N], r[N], siz[N], op[N];

int dfs(int k){
    siz[k] = 1;
    if(l[k] != -1) siz[k] += dfs(l[k]);
    if(r[k] != -1) siz[k] += dfs(r[k]);
    return siz[k];
}

bool check(int x, int y){
    if(x==-1 && y==-1) return true;
    else if(x != -1 && y != -1){
        if(v[x]==v[y])
            return check(l[x], r[y])&&check(r[x], l[y]);
        else
            return false;
    }
    return false;
}

int main(){
    cin >> n;
    for(int i = 1; i <= n; i++)
        cin >> v[i];
    for(int i = 1; i <= n; i++)
        cin >> l[i] >> r[i];
    dfs(1);
    for(int i = 1; i <= n; i++){    
        if(check(l[i], r[i]))
            ans = max(ans, siz[i]);
    }
    cout << ans;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值