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
解析
考点::枚举、模拟
思路:
参考代码:
#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:
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≤500,m≤100,ti≤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。
#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;
}