NOIP2018 普及组复赛题解

A:标题统计

题目链接

题意

给你一个可能含空格和换行符的字符串。让你求出其中不包含空格和换行的字符数量。

思路

大水题,直接根据题意一次判断每个字符,然后统计答案即可。

代码

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

B:龙虎斗

题目链接

题意

n n n 个兵营,开始时第 i i i 个兵营里面有 a i a_i ai 个兵。然后第 p 1 p_1 p1 号兵营里面多出了 s 1 s_1 s1 个兵。现在定义龙的气势和为所有在 m m m 号兵营左侧的兵营的兵数与与 m m m 号军营之间的距离的乘积的和。即 ∑ i = 1 m − 1 a i ⋅ ( m − i ) \sum\limits_{i=1}^{m-1} a_i\cdot (m-i) i=1m1ai(mi)。虎的气势和同理,只不过在 m m m 号兵营的右侧。现在你手里有 s 2 s_2 s2 个兵。你可以将他们派入任何一个兵营,求派入哪个兵营能取得最少的龙虎气势差。

思路

先把龙和虎的气势和算出来,然后枚举派到每一个兵营,计算龙虎气势只差,统计答案。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
inline int read();
int n;
int a[N];
int m,p1,s1,p;
int sum1,sum2;
int wz,ans;
int get(int x){//计算龙虎气势差
	int s1=sum1,s2=sum2;
	if(x<m)s1+=p*(m-x);
	else s2+=p*(x-m);
	return abs(s1-s2);
}
signed main(){
	n=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	m=read(),p1=read(),s1=read(),p=read();
	a[p1]+=s1;
	for(int i=1;i<m;i++)
		sum1+=a[i]*(m-i);
	for(int i=m+1;i<=n;i++)
		sum2+=a[i]*(i-m);
	wz=1,ans=get(1);
	for(int i=2;i<=n;i++){
		if(get(i)<ans)
			ans=get(i),wz=i;
	}
	printf("%d\n",wz);
	return 0;
}
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}

题目链接

C:摆渡车

题意

n n n 个人,第 i i i 个人会在第 a i a_i ai 分钟取等车。已知大巴来回一趟所需 m m m 分钟。求最少的这 n n n 个人的等待时间总长

思路

看到题目,应该一眼的 DP 裸题。

我们定义两个数组:cntsum

其中 c n t i cnt_i cnti 表示第 0 0 0 分钟到第 i i i 分钟到车站的同学数。

s u m i sum_i sumi 表示第 0 0 0 分钟到第 i i i 分钟到车站等车的同学的到达时间之和

那么我们考虑怎么计算:一辆大巴从第 r r r 分钟开出,等车时间在 l ∼ r l\sim r lr (含 l l l)的同学所对应的等待时间总长

我们不妨设这些同学的等车时间分别为: b 1 , b 2 , … , b t b_1,b_2,\ldots,b_t b1,b2,,bt,则总长应该是: ∑ i = 1 t r − b i \sum\limits_{i=1}^t r-b_i i=1trbi。化简一下,原式可变为: ∑ i = 1 t r − ∑ i = 1 t b i = t ⋅ r − ∑ i = 1 t b i \sum\limits_{i=1}^t r-\sum_{i=1}^t b_i=t\cdot r-\sum\limits_{i=1}^t b_i i=1tri=1tbi=tri=1tbi

首先,显然同学的数量 t t t 是等于 c n t r − c n t l − 1 cnt_r-cnt_{l-1} cntrcntl1 的。而 ∑ b i \sum b_i bi 是等于 s u m r − s u m l − 1 sum_r-sum_{l-1} sumrsuml1 的。

所以原式可化简为: r ⋅ ( c n t r − c n t l − 1 ) − ( s u m r − s u m l − 1 ) r\cdot (cnt_r-cnt_{l-1})-(sum_r-sum_{l-1}) r(cntrcntl1)(sumrsuml1)

我们记它为 calc(l,r)

我们现在定义 f i f_i fi 表示第 i i i 分钟发车,前面时刻所有同学都上车所需花费的最小等车时间之和

如果之前都没法过车,那么 f i = i ⋅ c n t i − s u m i f_i=i\cdot cnt_i-sum_i fi=icntisumi

如果之前发过车,那么我们枚举 j j j 1 ∼ i − m 1\sim i-m 1im,表示它上一辆大巴是从 j j j 时刻发车的。

那么可以得出转移式: f i = min ⁡ j = 1 i − m ( f j + c a l c ( j + 1 , i ) ) f_i=\min\limits_{j=1}^{i-m} (f_j+calc(j+1,i)) fi=j=1minim(fj+calc(j+1,i))

注意:这里从 j + 1 j+1 j+1 开始枚举,因为第 j j j 分钟上车的人已经被第 j j j 分钟发车的大巴带走了。

还有一点:我们这里的 i i i 要枚举到 m a x t + m − 1 maxt+m-1 maxt+m1 m a x t maxt maxt 表示读入时间的最大值),因为倒数第二辆大巴可能在 [ m a x t − m , m a x t − 1 ] [maxt-m,maxt-1] [maxtm,maxt1] 之间发车

这样的时间复杂度是 θ ( T 2 ) \theta(T^2) θ(T2) 的,期望得分: 50  pts 50~\text{pts} 50 pts

接下来我们考虑如何优化。

我们在对于 f i f_i fi 的转移中,我们枚举的 j j j 的复杂度是 θ ( n ) \theta(n) θ(n),但是真正能起到有效转移的,应该只有 i − 2 ⋅ m + 1 ∼ i − m i-2\cdot m+1\sim i-m i2m+1im 之间的 j j j。因为你考虑到,在 i − 2 ⋅ m i-2\cdot m i2m 发出的车,一定没有 i − m i-m im 优。

这样,我们的转移式即变为:

f i = min ⁡ j = i − 2 ⋅ m + 1 i − m ( f j + c a l c ( j + 1 , i ) ) f_i=\min\limits_{j=i-2\cdot m+1}^{i-m} (f_j+calc(j+1,i)) fi=j=i2m+1minim(fj+calc(j+1,i))

这样的时间复杂度变为了 θ ( m ⋅ T ) \theta(m\cdot T) θ(mT),期望得分: 70  pts 70~\text{pts} 70 pts

注意到 n n n m m m 只有 500 500 500 的大小,说明会有很多段长度为 m m m,但中间却没有人等车。

判定条件就是 i ⩾ m i\geqslant m im c n t i = c n t i − m cnt_i=cnt_{i-m} cnti=cntim

那么我们可以直接将 f i f_i fi 赋值成 f i − m f_i-m fim

代码

50分代码
#include<bits/stdc++.h>
using namespace std;
const int N=601,M=5e6;
inline int read();
int n,m;
int t[N];
int cnt[M];//1~i中等车的人数 
int sum[M];
int f[M];
int maxT,ans=1e9+7;
int calc(int l,int r){
	return (cnt[r]-cnt[l-1])*r-(sum[r]-sum[l-1]);
}
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++)
	{
		t[i]=read();
		maxT=max(maxT,t[i]);
		cnt[t[i]]++;
		sum[t[i]]+=t[i];
	}
	for(int i=1;i<=maxT+m-1;i++){
		cnt[i]+=cnt[i-1];
		sum[i]+=sum[i-1];
	}
	for(int i=1;i<=maxT+m-1;i++){
		f[i]=i*cnt[i]-sum[i];
		for(int j=0;j<=i-m;j++){
			f[i]=min(f[i],f[j]+calc(j+1,i));
		}
	}
	for(int i=maxT;i<=maxT+m-1;i++)
		ans=min(ans,f[i]);
	printf("%d\n",ans);
	return 0;
}
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
70分代码
#include<bits/stdc++.h>
using namespace std;
const int N=601,M=5e6;
inline int read();
int n,m;
int t[N];
int cnt[M];//1~i中等车的人数 
int sum[M];
int f[M];
int maxT,ans=1e9+7;
int calc(int l,int r){
	return (cnt[r]-cnt[l-1])*r-(sum[r]-sum[l-1]);
}
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++)
	{
		t[i]=read();
		maxT=max(maxT,t[i]);
		cnt[t[i]]++;
		sum[t[i]]+=t[i];
	}
	for(int i=1;i<=maxT+m-1;i++){
		cnt[i]+=cnt[i-1];
		sum[i]+=sum[i-1];
	}
	for(int i=1;i<=maxT+m-1;i++){
		f[i]=i*cnt[i]-sum[i];
		for(int j=max(i-2*m+1,0);j<=i-m;j++){
			f[i]=min(f[i],f[j]+calc(j+1,i));
		}
	}
	for(int i=maxT;i<=maxT+m-1;i++)
		ans=min(ans,f[i]);
	printf("%d\n",ans);
	return 0;
}
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
100分代码
#include<bits/stdc++.h>
using namespace std;
const int N=601,M=5e6;
inline int read();
int n,m;
int t[N];
int cnt[M];//1~i中等车的人数 
int sum[M];
int f[M];
int maxT,ans=1e9+7;
int calc(int l,int r){
	return (cnt[r]-cnt[l-1])*r-(sum[r]-sum[l-1]);
}
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++)
	{
		t[i]=read();
		maxT=max(maxT,t[i]);
		cnt[t[i]]++;
		sum[t[i]]+=t[i];
	}
	for(int i=1;i<=maxT+m-1;i++){
		cnt[i]+=cnt[i-1];
		sum[i]+=sum[i-1];
	}
	for(int i=1;i<=maxT+m-1;i++){
		f[i]=i*cnt[i]-sum[i];
		if(i>=m&&cnt[i]==cnt[i-m]){
			f[i]=f[i-m];
			continue;
		} 
		for(int j=max(i-2*m+1,0);j<=i-m;j++){
			f[i]=min(f[i],f[j]+calc(j+1,i));
		}
	}
	for(int i=maxT;i<=maxT+m-1;i++)
		ans=min(ans,f[i]);
	printf("%d\n",ans);
	return 0;
}
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}

D:对称二叉树

题目链接

题意

你有一颗 n n n 个节点的二叉树,每个节点有一个权值 v a l val val,若称一个节点是好的,当且仅当这以这个节点为根的二叉树对于每个节点都将左右子树调换后所形成的新树与原树同一位置的点点权全部相同。求出最大的好的节点的子节点个数。

思路

这里介绍两种思路。

第一种:暴力判断

你对于每一个节点,都以它为根。暴力判断一下是否可行,然后统计答案。时间复杂度 θ ( 可过 ) \theta(可过) θ(可过).

第二种:树哈希

你求出这棵树的两种遍历方式(根左右和根右左),放在一个哈希里面。然后对于每一个节点,若它的两种遍历形式所对应的哈希值都相同,则说明以它为根的子树满足题目所述条件,更新答案即可。时间复杂度 θ ( n ) \theta(n) θ(n)

代码

第一种:暴力判断
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5,hs1=1e9+1,hs2=1e9+7,hs3=1e9+9;
inline int read();
int n,rt,ans;
int l[N],r[N],val[N],sum[N];
bool b[N];
void getroot(){
	for(int i=1;i<=n;i++)
		b[l[i]]=1,b[r[i]]=1;
	for(int i=1;i<=n;i++)
		if(!b[i]){
			rt=i;
			return;
		}
}
void getsum(int x){//求子数大小
	sum[x]=1;
	if(l[x])getsum(l[x]),sum[x]+=sum[l[x]];
	if(r[x])getsum(r[x]),sum[x]+=sum[r[x]];
	return;
}
bool check(int rt1,int rt2){
	if(val[rt1]!=val[rt2]||sum[rt1]!=sum[rt2])return 0;
	if(l[rt1]&&!r[rt2]||r[rt1]&&!l[rt2])return 0;
	if(!l[rt1]&&!l[rt2])return 1;
	return check(l[rt1],r[rt2])&&check(r[rt1],l[rt2]);
}
signed main(){
	n=read();
	for(int i=1;i<=n;i++)
		val[i]=read();
	for(int i=1;i<=n;i++){
		l[i]=read(),r[i]=read();
		l[i]+=(l[i]==-1),r[i]+=(r[i]==-1);
	}
	getroot();
	getsum(rt);
	for(int i=1;i<=n;i++){
		if(check(l[i],r[i])){
			ans=max(ans,sum[i]);
		}
	}
	printf("%lld\n",ans);
	return 0;
}
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
第二种:树哈希

我的代码用了保险一点的三哈希

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5,hs1=1e9+1,hs2=1e9+7,hs3=1e9+9;
inline int read();
int n,rt,ans;
int l[N],r[N],val[N],sum[N];
bool b[N];
void getroot(){
	for(int i=1;i<=n;i++)
		b[l[i]]=1,b[r[i]]=1;
	for(int i=1;i<=n;i++)
		if(!b[i]){
			rt=i;
			return;
		}
}
struct sa{
	int hs1,hs2,hs3;
}ans1[N],ans2[N];
int getlen(int x){
	int c=0;
	while(x)
		x/=10,c++;
	return c+10;
}
int qow(int a,int b,int c){
	int ans=1;
	while(b){
		if(b&1)ans=ans*a%c;
		b>>=1,a=a*a%c;
	}
	return ans;
}
int calc(int x,int y,int op){
	if(op==1)op=hs1;
	if(op==2)op=hs2;
	if(op==3)op=hs3;
	return (x*qow(10,getlen(y),op)%op+y%op)%op;
}
void glr(int x){//根左右
	if(!l[x]&&!r[x]){
		ans1[x].hs1=ans1[x].hs2=ans1[x].hs3=val[x];
		return; 
	}
	if(l[x])glr(l[x]);
	if(r[x])glr(r[x]);
	ans1[x].hs1=calc(calc(val[x],ans1[l[x]].hs1,1ll),ans1[r[x]].hs1,1ll);
	ans1[x].hs2=calc(calc(val[x],ans1[l[x]].hs2,2ll),ans1[r[x]].hs2,2ll);
	ans1[x].hs3=calc(calc(val[x],ans1[l[x]].hs3,3ll),ans1[r[x]].hs3,3ll);
	return;
}
void grl(int x){//根右左
	if(!l[x]&&!r[x]){
		ans2[x].hs1=ans2[x].hs2=ans2[x].hs3=val[x];
		return; 
	}
	if(r[x])grl(r[x]);
	if(l[x])grl(l[x]);
	ans2[x].hs1=calc(calc(val[x],ans2[r[x]].hs1,1ll),ans2[l[x]].hs1,1ll);
	ans2[x].hs2=calc(calc(val[x],ans2[r[x]].hs2,2ll),ans2[l[x]].hs2,2ll);
	ans2[x].hs3=calc(calc(val[x],ans2[r[x]].hs3,3ll),ans2[l[x]].hs3,3ll);
	return;
}
void getsum(int x){//求子数大小
	sum[x]=1;
	if(l[x])getsum(l[x]),sum[x]+=sum[l[x]];
	if(r[x])getsum(r[x]),sum[x]+=sum[r[x]];
	return;
}
bool check(int x){//判断是否可行
	return (ans1[x].hs1==ans2[x].hs1&&ans1[x].hs2==ans2[x].hs2&&ans1[x].hs3==ans2[x].hs3);
}
signed main(){
	n=read();
	for(int i=1;i<=n;i++)
		val[i]=read();
	for(int i=1;i<=n;i++){
		l[i]=read(),r[i]=read();
		l[i]+=(l[i]==-1),r[i]+=(r[i]==-1);
	}
	getroot();
	glr(rt),grl(rt);
	getsum(rt);
	for(int i=1;i<=n;i++)
		if(check(i))
			ans=max(ans,sum[i]);
	printf("%lld\n",ans);
	return 0;
}
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}

但事实证明暴力却比哈希跑得快?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: NOIP2008 普及复赛试题是中国国家信息学奥林匹克选手选拔赛的试题之一,比较经典且有一定难度。以下是对该试题的解答。 本次试题主要有三道题目,分别是小数排列、整数取位和检测型括号序列。 小数排列这道题要求给定一个正整数 n,求小于 n 的所有符合要求的小数的个数。解决这一题的方法是利用排列合的知识,找出符合要求的小数的模式并计算其个数。具体的代码实现就需要对 n 进行拆分,计算个位数、十位数和百位数的可能情况并相乘即可得到结果。 整数取位这道题要求给定一个整数 n 和一个非负整数 m,求 n 的第 m 个数字。解决这一题的方法是将整数 n 转化为字符串,然后通过字符串的索引来获取第 m 个数字。 检测型括号序列这道题要求判断给定的一个仅包含左右括号的字符串是否是合法的括号序列。解决这一题的方法是使用栈的数据结构,遍历字符串,对于每个遇到的左括号,将其压入栈中;对于每个遇到的右括号,检查栈顶元素是否为对应的左括号,若是,则弹出栈顶元素,否则返回不合法。 以上就是对 NOIP2008 普及复赛试题的简要解答,其中涉及到的算法数据结构是编程中比较常见的基础知识,通过理解和掌握这些知识,可以帮助我们更好地解决类似的编程问题。 ### 回答2: NOIP(全国青少年信息学奥林匹克竞赛)是中国举办的一项信息学竞赛活动。2008年,NOIP举办了普及复赛。以下是对该年度复赛试题进行的解答。 复赛试题一共有三大题目,分别涉及到图的遍历、数学运算和字符串处理。 第一题是关于图的遍历的。题目给出一张有向图和一个起始节点,要求按照拓扑排序的原则遍历整个图,并输出遍历的结果。拓扑排序是一种将有向无环图的顶点进行排序的算法,具体实现可以使用DFS或者BFS。根据题目给出的起始节点,我们可以使用DFS从该节点开始遍历图,并使用一个栈来存储遍历的结果。 第二题是一个数学运算的题目,要求计算一个给定数的乘方结果的各位数字之和。这题可以通过将给定数转化为字符串,然后对字符串中的每位数字进行相加来解答。也可以将给定数分解为各位数字相加的形式。具体实现上可以使用循环或者递归的方式。 第三题是一个关于字符串处理的任务,要求将输入字符串中的数字字符提取出来,并计算所有数字的平均值。这个问题可以通过遍历字符串的方式来解决。对于每个字符,我们判断是否为数字字符,是的话就将其转换为数字并累加到一个总和上。最后将总和除以数字字符的个数,得到平均值。 总体来说,NOIP 2008 普及复赛试题涵盖了图的遍历、数学运算和字符串处理的内容。通过解答这些问题,可以增强对这些概念的理解,并提升解决实际问题的能力。 ### 回答3: NOIP2008普及复赛试题是一道考察动态规划和递归思想的题目。题目给出了一个整数n,要求计算出整数1到n的所有排列中,满足以下条件的排列的个数: 1.相邻两个数的差的绝对值不能等于1; 2.排列中的数不能重复。 首先,我们需要定义一个函数f(n),表示整数1到n的满足条件的排列的个数。我们可以将这个问题转化为子问题,即如何计算f(n-1)和f(n-2)等。 根据题目要求,我们可以发现f(n)的值由两部分成:一部分是以n结尾的满足条件的排列的个数,另一部分是不以n结尾的满足条件的排列的个数。 对于第一部分,即以n结尾的排列,我们可以将其分为两种情况:n和n-2相邻,以及n和n-2不相邻。如果n和n-2相邻,那么有f(n-2)种情况。如果n和n-2不相邻,那么可以在以n-1结尾的排列后面加上n,所以有f(n-1)种情况。因此,以n结尾的满足条件的排列的个数为f(n) = f(n-1) + f(n-2)。 对于第二部分,即不以n结尾的排列,其个数就是f(n-1)。 所以,f(n) = f(n-1) + f(n-2) + f(n-1) = 2*f(n-1) + f(n-2)。 基本情况是当n=1时,满足条件的排列只有1个,即f(1)=1;当n=2时,满足条件的排列有2个,即f(2)=2。 通过递推,可以得到整个解空间中满足条件的排列的个数。 在编程实现时,可以使用动态规划来解决这道题。先定义一个大小为n+1的数dp,dp[i]表示整数1到i的满足条件的排列的个数。然后,通过循环从3到n,依次计算dp[i]的值,最后返回dp[n]即可。 总结起来,这道题是通过递推和动态规划的思想来计算满足条件的排列的个数。通过定义状态转移方程,将大问题转化为小问题,最后通过循环计算得出结果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值