2019第十届蓝桥杯国赛c++B组真题及题解

幸运地水了个国一,应该是对了3个结果填空,2+0.5+0.5个编程大题。
此次国赛共有十个题:
A~E 五个结果填空,F~J 五个编程大题。
目前并未找到标准答案,个人解法可能并不完全正确或最优,不对的还请指正!

A-平方序列:

小明想找到两个正整数X和Y,满足
1、2019<X<Y
2、20192、X2、Y2构成等差数列
请你求出在所有可能的解中,X+Y的最小值是多少?

核心思想:

一重循环。
从2020开始遍历X的值,对于每一个X的值,利用等差数列的条件求出Y2的值,只需要判断开方得到的Y是不是整数即可。(可以将Y再次平方,看数值是否发生变化)

代码如下:

#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
int main()
{
	int t=2019*2019;
	for(int x=2020;;x++)
	{
		int x2=x*x;
		int y2=2*x2-t;
		int y=sqrt(y2);
		if(y*y==y2)
		{
			cout<<x+y<<endl;
			break;
		}
	}
	return 0;
}

答案:

7020

B-质数拆分:

将2019拆分为若干个两两不同的质数之和,一共有多少种不同的方法?注意交换顺序视为同一种方法,例如2 + 2017 = 2019与2017 + 2 = 2019视为同一种方法。

核心思想:

先用欧拉线性筛法将素数筛出来,存在pre数组中。

dp[i][pre[j]]表示满足下列额外条件的分解方案数:
1、被分解数为i
2、分解出的素数最大值恰好为pre[j]
状态转移方程:
for(int k=0;k<j&&pre[k]<=i-pre[j];k++)
  dp[i][pre[j]]+=dp[i-pre[j]][pre[k]];

代码如下:

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e4+20;
const int M=2019;
int pre[N],cnt;
bool vis[N];
ll dp[N][N];
void xss()
{
	for(int i=2;i<N-10;i++)
	{
		if(!vis[i])
			pre[cnt++]=i;
		for(int j=0;j<cnt&&i*pre[j]<N-5;j++)
		{
			vis[i*pre[j]]=1;
			if(i%pre[j]==0)break;
		}
	}
	return;
}
int main()
{
	xss();
	for(int i=0;pre[i]<=M;i++)
		dp[pre[i]][pre[i]]=1;
	for(int i=2;i<=M;i++)
	{
		for(int j=0;pre[j]<=i;j++)
		{
			for(int k=0;k<j&&pre[k]<=i-pre[j];k++)
				dp[i][pre[j]]+=dp[i-pre[j]][pre[k]];
		}
	}
	ll ans=0;
	for(int i=0;pre[i]<=M;i++)
		ans+=dp[M][pre[i]];
	cout<<ans<<endl;
	return 0;
}

答案:

55965365465060

C-拼接:

小明要把一根木头切成两段,然后拼接成一个直角。如下图所示,他把中间部分分成了nXn的小正方形,他标记了每个小正方形属于左边还是右边。然后沿两边的分界线将木头切断,将右边旋转向上后拼接在一起。
在这里插入图片描述要求每个小正方形都正好属于左边或右边,而且同一边的必须是连通的。在拼接时,拼接的部位必须保持在原来大正方形里面。请问,对于7的小正方形,有多少种合法的划分小正方形的方式。

题解待更新……

D-求值:

学习了约数后,小明对于约数很好奇,他发现,给定一个正整数t,总是可以找到含有t个约数的整数。小明对于含有t个约数的最小数非常感兴趣,并把它定义为St。例如S1= 1,S2= 2,S3= 4,S4= 6,。现在小明想知道,当t= 100时,St是多少?即S100是多少?

核心思想:

两重for循环暴力。

代码如下:

#include<cstdio>
#include<iostream>
using namespace std;
int main()
{
	for(int i=1;;i++)
	{
		int sum=0;
		for(int j=1;j<=i;j++)
		{
			if(i%j==0)
				sum++;
		}
		if(sum==100)
		{
			cout<<i<<endl;
			break;
		}
	}
	return 0;
}

答案:

45360

E-路径计数:

有一个7X7的方格。方格左上角顶点坐标为(0,0),右下角坐标为(7,7)。
求满足下列条件的路径条数:
1、起点和终点都是(0,0)
2、路径不自交
3、路径长度不大于12
4、对于每一个顶点,有上下左右四个方向可以走,但是不能越界。

核心思想:

dfs,根据路径长度不大于12来剪枝。

代码如下:

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=25;
int ans;
bool vis[N][N];
int b[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
void dfs(int x,int y,int k)
{
	if(k>12)
		return;
	for(int i=0;i<4;i++)
	{
		int tx=x+b[i][0];
		int ty=y+b[i][1];
		if(!tx&&!ty&&k+1>2&&k+1<=12)
			ans++;
		if(tx<0||tx>7||ty<0||ty>7)continue;
		if(vis[tx][ty])continue;
		vis[tx][ty]=1;
		dfs(tx,ty,k+1);
		vis[tx][ty]=0;
	}
	return;
}
int main()
{
	vis[0][0]=1;
	dfs(0,0,0);
	vis[0][0]=0;
	cout<<ans<<endl;
	return 0;
}

答案:

206

F-最优包含:

题目给定两个字符串S和T,保证S的长度不小于T的长度,问至少修改S的多少个字符,可以令T成为S的子序列。

输入描述:

两行。
第一行是字符串S,第二行是字符串T。
保证S的长度不小于T的长度,S的长度范围在10~1000之间。

输出描述:

答案,一个非负整数。

输入样例:

XBBBBBAC
ACC

输出样例:

2

核心思想:

动态规划。
dp[i][j]表示令T的前j个字符成为S的前i个字符子序列需要修改的字符个数。
先初始化i=j和j=0的情况。
状态转移方程:
if(s[i]==t[j])
  dp[i][j]=dp[i-1][j-1];
else
  dp[i][j]=min(dp[i-1][j],dp[i-1][j-1]+1);

代码如下:

#include<cstdio>
#include<iostream>
#include<string>
using namespace std;
const int N=1e3+10;
int dp[N][N];
string s,t;
int main()
{
	cin>>s>>t;
	int ls=s.size();
	int lt=t.size();
	if(s[0]!=t[0])
		dp[0][0]=1;
	for(int i=1;i<lt;i++)
		if(s[i]==t[i])
			dp[i][i]=dp[i-1][i-1];
		else
			dp[i][i]=dp[i-1][i-1]+1;
	for(int i=1;i<ls;i++)
	{
		if(s[i]==t[0])
			dp[i][0]=0;
		else
			dp[i][0]=dp[i-1][0];
	}
	for(int j=1;j<lt;j++)
		for(int i=j+1;i<ls;i++)
			if(s[i]==t[j])
				dp[i][j]=dp[i-1][j-1];
			else
				dp[i][j]=min(dp[i-1][j],dp[i-1][j-1]+1);
	printf("%d\n",dp[ls-1][lt-1]);
	return 0;
}

G-排列数:

对于一个数列中的某个数,如果这个数比两侧的数都大比两侧的数都小,我们称这个数为这个数列的一个转折点。
如果一个数列有t个转折点,我们称这个数列为t+1调数列。
给定两个正整数n,k。求在1~n的全排列中,有多少个数列是k调数列。

输入描述:

两个正整数n,k。

输出描述:

答案,一个整数。

输入样例:

4 2

输出样例:

12

核心思想:

利用全排列的递归函数,剪枝,水数据。

代码如下:

#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e3+10;
int n,k,ans,a[N];
void swap(int x,int y)
{
	int t=a[x];
	a[x]=a[y];
	a[y]=t;
	return;
}
bool pd(int x)//判断a[x]是不是转折点 
{
	if(a[x]>a[x-1]&&a[x]>a[x+1]||a[x]<a[x-1]&&a[x]<a[x+1])
		return 1;
	return 0;
}
void dfs(int m,int sum)//m为递归层数,sum为已经有的转折点数量 
{
	if(m>2&&pd(m-2))//判断a[m-2]是不是转折点 
		sum++;
	if(sum>k-1||sum+n-m<k-1)//转折点过多或者过少都剪枝 
		return;
	if(m==n-1)//全排列递归出口 
	{
		if(m>1&&pd(m-1))
			sum++;
		if(sum==k-1)
			ans++;
		return;
	}
	for(int i=m;i<n;i++)//全排列函数的函数主体 
	{
		swap(m,i);
		dfs(m+1,sum);
		swap(m,i);
	}
	return;
}
int main()
{
	cin>>n>>k;
	for(int i=0;i<n;i++)//初始化数组 
		a[i]=i+1;
	dfs(0,0);
	cout<<ans<<endl;
	return 0;
}

H-解谜游戏:

在这里插入图片描述小明正在玩一款解谜游戏。谜题由24根塑料棒组成,其中黄色塑料棒4根,红色8根,绿色12根(后面用Y表示黄色、R表示红色、G表示绿色)。初始时这些塑料棒排成三圈,如上图所示,外圈12根,中圈8根,内圈4根。小明可以进行三种操作:

1.将三圈塑料棒都顺时针旋转一个单位。例如当前外圈从0点位置开始顺时针依次是YRYGRYGRGGGG,中圈是RGRGGRRY,内圈是GGGR。那么顺时针旋转一次之后,外圈、中圈、内圈依次变为:GYRYGRYGRGGG、YRGRGGRR和RGGG。

2.将三圈塑料棒都逆时针旋转一个单位。例如当前外圈从0点位置开始顺时针依次是YRYGRYGRGGGG,中圈是RGRGGRRY,内圈是GGGR。那么逆时针旋转一次之后,外圈、中圈、内圈依次变为:RYGRYGRGGGGY、GRGGRRYR和GGRG

3.将三圈0点位置的塑料棒做一个轮换。具体来说:外圈0点塑料棒移动到内圈0点,内圈0点移动到中圈0点,中圈0点移动到外圈0点。例如当前外圈从0点位置开始顺时针依次是YRYGRYGRGGGG,中圈是RGRGGRRY,内圈是GGGR。那么轮换一次之后,外圈、中圈、内圈依次变为:RRYGRYGRGGGG、GGRGGRRY和YGGR。小明的目标是把所有绿色移动到外圈、所有红色移动中圈、所有黄色移动到内圈。给定初始状态,请你判断小明是否可以达成目标。

输入描述:

第一行包含一个整数T,代表询问的组数。(1<T<100)。每组询问包含3行:第一行包含12个大写字母,代表外圈从0点位置开始顺时针每个塑料棒的颜色。第二行包含8个大写字母,代表中圈从0点位置开始顺时针每个塑料棒的颜色。第三行包含4个大写字母,代表内圈从0点位置开始顺时针每个塑料棒的颜色。

输出描述:

对于每组询问,输出一行YES或者NO,代表小明是否可以达成目标。

输入样例:

2
GYGGGGGGGGGG
RGRRRRRR
YRYY
YGGGRRRRGGGY
YGGGRRRR
YGGG

输出样例:

YES
NO

题解待更新……

I-第八大奇迹:

在一条R河流域,繁衍着一个古老的名族Z。他们世代沿河而居,也在河边发展出了璀璨的文明。
Z族在R河沿岸修建了很多建筑,最近,他们热衷攀比起来。他们总是在比谁的建筑建得最奇特。
幸好Z族人对奇特的理解都差不多,他们很快给每栋建筑都打了分,这样评选谁最奇特就轻而易举了。
于是,根据分值,大家很快评出了最奇特的建筑,称为大奇迹。
后来他们又陆续评选了第二奇特、第二奇特、…、第七奇特的建筑,依次称为第二大奇迹、第三大奇迹、…、第七大奇迹。
最近,他们开始评选第八奇特的建筑,准备命名为第八大奇迹。在评选中,他们遇到了一些问题。
首先,Z族一直在发展,有的建筑被拆除又建了新的建筑,新建筑的奇特值和原建筑不一样,这使得评选不那么容易了。
其次,Z族的每个人所生活的范围可能不一样,他们见过的建筑并不是所有的建筑,他们坚持他们自己所看到的第八奇特的建筑就是第八大奇迹。
Z族首领最近很头疼这个问题,他害怕因为意见不一致导致Z族发生分歧。他找到你,他想先了解一下,民众自己认为的奇迹是怎样的。
现在告诉在R河周边的建筑的变化情况,以及在变化过程中一些人的生活范围,请编程求出每个人认为的第八大奇迹的奇特值是多少

输入描述:

输入的第一行包含两个整数L,N,分别表示河流的长度和要你处理的信息的数量。开始时河流沿岸没有建筑,或者说所有的奇特值为0。接下来N行,每行一条你要处理的信息。
如果信息为C p x,表示流域中第p个位置(1<=p<=L)建立了一个建筑,其奇特值为x。如果这个位置原来有建筑,原来的建筑会被拆除。
如果信息为Q a b,表示有个人生活的范围是河流的第a到b个位置(包含a和b,a<=b),这时你要算出这个区间的第八大奇迹的奇特值,并输出。如果找不到第八大奇迹,输出0。

输出描述:

对于每个为Q的信息,你需要输出一个整数,表示区间中第八大奇迹的奇特值。

输入样例:

10 14
C 1 5
C 2 4
C 3 7
C 4 6
C 5 5
C 6 1
C 7 8
Q 1 10
C 8 3
C 9 6
C 10 3
Q 1 9
C 6 10
Q 1 10

输出样例:

0
3
4

核心思想:

线段树+sort
线段树基础戳这里

代码如下:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=1e5+20;
int ans[17];//用于输出 
char s[12];//用于读入字符C和Q 
struct node{
	int l,r;
	int a[17];
}tr[N<<2];
bool pd(int x,int y)//sort排序依据 
{
	if(x>y)
		return 1;
	return 0;
}
void pushup(int m)//线段树回溯函数 
{
	for(int i=1;i<=8;i++)
		tr[m].a[i]=tr[m<<1].a[i];
	for(int i=9;i<=16;i++)
		tr[m].a[i]=tr[m<<1|1].a[i-8];
	sort(tr[m].a+1,tr[m].a+17,pd);//及时sort 
	return;
}
void build(int m,int l,int r)//建立线段树 
{
	tr[m].l=l;
	tr[m].r=r;
	if(l==r)
	{
		memset(tr[m].a,0,sizeof(tr[m].a));
		return;
	}
	int mid=(l+r)>>1;
	build(m<<1,l,mid);
	build(m<<1|1,mid+1,r);
	pushup(m);
	return;
}
void update(int m,int x,int y)//更新数据 
{
	if(tr[m].l==x&&tr[m].r==x)
	{
		tr[m].a[1]=y;
		return;
	}
	int mid=(tr[m].l+tr[m].r)>>1;
	if(x<=mid)
		update(m<<1,x,y);
	else
		update(m<<1|1,x,y);
	pushup(m);
	return;
}
void query(int m,int l,int r)//查询数据 
{
	if(tr[m].l==l&&tr[m].r==r)
	{
		for(int i=9;i<=16;i++)
			ans[i]=tr[m].a[i-8];
		sort(ans+1,ans+17,pd);//及时sort 
		return;
	}
	int mid=(tr[m].l+tr[m].r)>>1;
	if(r<=mid)
		query(m<<1,l,r);
	else if(l>mid)
		query(m<<1|1,l,r);
	else
	{
		query(m<<1,l,mid);
		query(m<<1|1,mid+1,r);
	}
	return;
}
int main()
{
	int n,k,x,y;
	cin>>n>>k;
	build(1,1,n);
	for(int i=0;i<k;i++)
	{
		scanf("%s%d%d",s,&x,&y);
		if(s[0]=='C')
		{
			update(1,x,y);
		}
		else
		{
			for(int j=1;j<=8;j++)
				ans[j]=0;
			query(1,x,y);
			printf("%d\n",ans[8]);
		}
	}
	return 0;
}

J-燃烧权杖:

小C最近迷上了一款游戏。现在,在游戏中,小C有一个英雄,生命值为x;敌人也有一个英雄,生命值为y。除此以外,还有k个士兵,生命值分别为a1、a2、…、ak。
现在小C打算使用一个叫做“燃烧权杖”的技能。“燃烧权杖”会每次等概率随机选择一个活着的角色(英雄或士兵),扣减其10点生命值,然后如果该角色的生命值小于或等于0,则该角色死亡,不会再被“燃烧权杖”选中。“燃烧权杖”会重复做上述操作,直至任意一名英雄死亡。
小C想知道使用“燃烧权杖”后敌方英雄死亡(即,小C的英雄存活)的概率。为了避免精度误差,你只需要输出答案模一个质数p的结果,具体见输出格式。

输入描述:

输入包含多组数据。输入第一行包含一个正整数T,表示数据组数。接下来T组,每组数据第一行包含四个非负整数x、y、p、k,分别表示小C的英雄的生命值、敌方英雄的生命值,模数和士兵个数。第二行包含k个正整数a1、a2、…、ak,分别表示每个士兵的生命值

输出描述:

对于每组数据,输出一行一个非负整数,表示答案模质数p的余数。可以证明,答案一定为有理数。设答案为a/b(a和b为互质的正整数),你输出的数为x,则你需要保证a与bx模p同余;也即,x= (a*b-1)mod p,其中b-1表示b模p的逆元,mod为取模运算。

题解待更新……

  • 59
    点赞
  • 228
    收藏
    觉得还不错? 一键收藏
  • 31
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值