第六届“智慧杯“大赛编程思维(C++普及组)赛后解析(详细)

预计阅读时间:30分钟

前言


        第六届“智慧杯”大赛就在前不久结束了,很多人都取得了很好的成绩。当然,由于题目难度每年都在增加,本次大赛有很多人都觉得难,或者发挥失常。后面是我对本次大赛进行的详细思路分析,供大家参考,来了解自己的问题所在。

        话不多说,开始本篇博客!


正文


第一题

题目名称:筛选可疑密码

时间限制:1000ms 内存限制:256MB 提交通过率:23%

个人难度评级:★★

题目描述

情报小组截获了若干个可疑密码,这些密码都是4位数。现在他们获得了一条最新情报:个位数与千位数的和 减去 十位数与百位数的和,结果是一个正数。请你帮助情报组筛选目前的可疑密码。

输入描述

输入两行,第一行是:筛选前可疑密码的个数n(0 < n \le 100)n(0<n≤100)。
第二行是:待筛选的nn个可疑密码,密码之间空格分隔。

输出描述

输出两行,第一行是:筛选后可疑密码的个数。
第二行是:筛选后的可疑密码,密码之间空格分隔(若个数为0,不输出任何密码)。

样例1

输入

4 9119 8248 7356 1151

输出

3 9119 8248 7356

样例2

输入

3 2881 3743 5762

输出

0

思路分析

将每个数拆分成千、百、十、个四个变量,在进行判断即可。

本题水题一道

参考代码

下附AC代码,可供参考

#include <bits/stdc++.h>
using namespace std;
int main()
{
	int count=0;
	int n;
	int a;
	int ans[101];
	cin>>n;
	int k=0;
	for(int i=0; i<n; i++)
	{
		cin>>a;
		int t=a;
		int qian=t/1000;
		t%=1000;
		int bai=t/100;
		t%=100;
		int shi=t/10;
		t%=10;
		int ge=t;
		int tmp=(qian+ge)-(shi+bai);
		if(tmp>0)
		{
			count++;
			ans[k++]=a;
		}
	}
	cout<<count<<endl;
	for(int i=0; i<k; i++)
	{
		cout<<ans[i]<<" ";
	}
	return 0;
}

总结

这道题挺水的,主要考察的只是数字拆分。


第二题

题目名称:质数时间

时间限制:1000ms 内存限制:256MB 提交通过率:1%(这属实有点低)

个人难度评级:★★★★

题目描述

如果把一年之中的某个时间写作 aa 月 bb 日 cc 时 dd 分 ee 秒的形式,当这五个数都为质
数时,我们把这样的时间叫做质数时间,现已知起始时刻是 2022 年的 aa 月 bb 日 cc 时 dd 分 ee 秒,终止时刻是 2022 年的 uu 月 vv 日 ww 时 xx 分 yy 秒,请你统计在这段时间中有多少个质数时间?

输入描述

输入共 (2 * T + 1)(2∗T+1) 行。第一行一个整数 TT ,代表共有 TT 组查询。
接下来 2 * T2∗T 行,对于每组查询,先输入一行五个整数 a、b、c、d、ea、b、c、d、e ,代表起始时刻是 aa 月 bb 日 cc 时 dd 分 ee 秒。再输入一行五个整数 u、v、w、x、yu、v、w、x、y,代表终止时刻是 uu 月 vv 日 ww 时 xx 分 yy 秒。
对于每组查询保证输入的起始时刻不晚于终止时刻。

输出描述

输出共 TT 行,一行一个整数,表示对于每组查询输入统计到的从 aa 月 bb 日 cc 时 dd 分 ee 秒到 uu 月 vv 日 ww 时 xx 分 yy 秒中质数时间的个数。多组查询结果用换行分隔。

样例1

输入复制

3 3 3 3 3 0 3 3 3 5 59 7 2 6 45 32 7 29 15 30 54 2 6 2 45 32 12 3 16 56 8

输出

34 24276 127449

提示

对于所有数据,保证1 \leq T \leq 10^{5}1≤T≤105 且 1\leq a, u\leq 121≤a,u≤12, 1 \leq b, v \leq311≤b,v≤31, 0 \leq c, w < 240≤c,w<24, 0 \leq d, x < 600≤d,x<60 , 0 \leq e, y < 600≤e,y<60。
每个测试点的数据规模及特点如下表所示。

测试点编号T的范围约定
1T = 1T=1起始时刻与终止时刻相同
21 \leq T \leq 10^{5}1≤T≤105起始时刻与终止时刻相同
3T = 1T=1起始时刻与终止时刻在同一个小时中
41 \leq T \leq 10^{5}1≤T≤105起始时刻与终止时刻在同一个小时中
5T = 1T=1起始时刻与终止时刻在同一个月中
61 \leq T \leq 10^{5}1≤T≤105起始时刻与终止时刻在同一个月中
71 \leq T \leq 101≤T≤10起始时刻与终止时刻相差不超过100000秒
81 \leq T \leq 101≤T≤10起始时刻与终止时刻相差不超过100000秒
91 \leq T \leq 10^{5}1≤T≤105

思路分析

我的思路(对7个):

例如:

5月6日11点5分4秒~5月9日12点3分16秒

开头:

5月6日11点5分4秒~5月6日23点59分59秒之间的素数时间

中间:

5月7日~5月8日之间的天数*17*17*9(59秒中有17个素数,59分钟里有17个素数,23小时里有9个素数)

结尾:

5月9日0点0分0秒~5月9日12点3分15秒

可理解为:开头结尾大模拟,中间单独计算

正确思路(全对):

考虑到题目是对多个数据进行素数判定的T组查询,查询数据量在100000以内,因此可用筛法甲前缀和的思想进行预处理。

·首先提前判处60以内的素数(筛法)

·以0时刻为起点,分别统计秒、分、时、日、月中素数时间个数存入相应数组,需注意不同月份天数是不一样的。

·对于每组查询,用终止时刻的素数时间个数减去起始时刻之前的素数时间个数即可,“以空间换时间”的思想,查询效率可控制在O(T)。

参考代码

方法1(对7个):开头结尾大模拟,中间单独计算。思路见“思路分析-我的思路”。

#include <iostream>
using namespace std;
int ss[60]={0,0,1,1,0,1,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1};
int days[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
int a,b,c,d,e , u,v,w,x,y;
int head(){
	if(ss[a]==0||ss[b]==0) return 0;
	int count=0;
	while(!(c>=24&&d==0&&e==0))
	{
		e++;
//		if(ss[c]==1&&ss[e]==1&&ss[e]==1) count++;
//		cout<<"count="<<count<<endl;
		if(e==60)
		{
			e=0;
			d++;
//			if(ss[c]==1&&ss[e]==1&&ss[e]==1) count++;
//			cout<<"count="<<count<<endl;
		}
		if(d==60)
		{
			d=0;
			c++;
//			if(ss[c]==1&&ss[e]==1&&ss[e]==1) count++;
//			cout<<"count="<<count<<endl;
		}
		if(ss[c]==1&&ss[d]==1&&ss[e]==1) count++;
	}
//	cout<<"head:maxn="<<count<<endl;
	return count;
}
int body(){
	int bb=b,aa=a,vv=v;
	bb++,vv--;
	int count=0;
	while(!(aa>=u&&bb>vv))
	{
//		cout<<"a="<<a<<" u="<<u<<" b="<<b<<" v="<<v<<endl;
//		cout<<"a="<<a<<" b="<<b<<" "<<endl;
		if(ss[aa]==1&&ss[bb]==1) count++;
		bb++;
		if(bb>days[aa])
		{
			aa++;
			bb=1;
		}
	}
	return count;
}
int tail(){
	if(ss[u]==0||ss[v]==0) return 0;
	int count=0;
	int h=0,m=0,s=0;
	while(!(h>=w&&m>=x&&s>=y)){
		s++;
//		if(ss[c]==1&&ss[e]==1&&ss[e]==1) count++;
		if(s==60){
			s=0;
			m++;
//			if(ss[c]==1&&ss[e]==1&&ss[e]==1) count++;
			if(m==60){
				m=0;
				h++;
			}
		}
		if(ss[s]==1&&ss[m]==1&&ss[h]==1) count++;
	}
//	cout<<"tail:maxn="<<count<<endl;
	return count;
}
int tsqk(){
	int count=0;
	if(ss[c]==1&&ss[d]==1&&ss[e]==1) count++;
	while(!(c>=w&&d>=x&&e>=y)){
		e++;
		if(ss[c]==1&&ss[d]==1&&ss[e]==1) count++;
		if(e==60){
			e=0;
			d++;
		}
		if(d==60){
			d=0;
			c++;
		}
	}
	return count;
}
int main(){
	int S;
	cin>>S;
	while(S--){
		cin>>a>>b>>c>>d>>e>>u>>v>>w>>x>>y;
		int count=0;
		int maxn=0;
		if(a==u&&b==v){
			if(ss[a]==1&&ss[b]==1)
			{
				maxn=tsqk();
			}
			else
			{
				maxn=0;
			}
		}
		else{
			maxn=body();
//			cout<<"maxn="<<maxn<<endl;
			maxn=maxn*17*17*9;
			maxn+=tail();
			maxn+=head();
		}
		cout<<maxn<<endl;
	}
	return 0;
}

考试的时候用的这个代码,只对了7个

⚠️注意⚠️---注意当a==u&&b==v时,如果a和b都不是素数,就说明不可能有一个答案,输出0。需要输出0的测试例占了三个。

方法二(全对)

#include <iostream>

using namespace std;
bool vis[65];//vis[i]是标记素数的数组
const int md[]={-1,31,28,31,30,31,30,31,31,30,31,30,31};
int second[65],minute[65],hour[25],day[35],month[15];
int a,b,c,d,e;
void init()//预处理
{
	vis[0]=vis[1]=1;
	for(int i=2; i<=60; i++)//筛法
	{
		if(!vis[i])
		{
			for(int j=i*2; j<=60; j+=i)
			{
				vis[j]=1;
			}
		}
		second[i]=second[i-1]+(!vis[i]);
	}
	for(int i=1; i<=60; i++)
	{
		minute[i]=minute[i-1]+(!vis[i]?second[60]:0);
	}
	for(int i=1; i<=24; i++)
	{
		hour[i]=hour[i-1]+(!vis[i]?minute[60]:0);
	}
	for(int i=1; i<=31; i++)
	{
		day[i]=day[i-1]+(!vis[i]?hour[24]:0);
	}
	for(int i=1; i<=12; i++)
	{
		month[i]=month[i-1]+(!vis[i]?day[md[i]]:0);
	}
}
int solve()//求解函数,计算从0时刻到a月b日c时d分e秒的质数时间个数
{
	int ret=0;
	ret+=month[a-1];
	if(vis[a])
	{
		return ret;
	}
	ret+=day[b-1];
	if(vis[b])
	{
		return ret;
	}
	ret+=hour[c-1];
	if(vis[c])
	{
		return ret;
	}
	ret+=minute[d-1];
	if(vis[d]||e==-1)
	{
		return ret;
	}
	return ret+second[e];
}
int main(int argc, char*argv[])
{
	init();
	int T;
	cin>>T;
	while(T--)
	{
		cin>>a>>b>>c>>d>>e;
		e--;
		int ans=-solve();
		cin>>a>>b>>c>>d>>e;
		ans+=solve();
		cout<<ans<<endl;
	}
	return 0;
}

 总结

本题还算简单,该拿到的分要拿到。考点时日历问题


第三题

题目名称:水果销售商

时间限制:1000ms 内存限制:256MB 提交通过率:1%(啊这啊这)

个人难度评级:★★★★

题目描述

小北家中经营了一个果园,因为水果质量广受好评,每一批水果成熟之后都会有大量销售商来购买。但是小北的父母不善于经营,在保证每一批水果都会卖完的情况同时每个销售商都不会空手而归的前提下,每个销售商水果数量都是随机的,小北对这个情况很不满意,他认为销售量高的VIP标准值更多的水果,而销售量低的普通销售商应该拿到比标准值更少的水果,这样才不会浪费,他想用自己学过的知识计算出每一批水果有多少种分配方式来说服父母,你能帮他算一下吗?

输入描述

共n+1n+1行

第一行为水果批次数量nn和水果数量标准值kk。

接下来的nn行为每行依次:VIP供货商数量aa,普通供货商数量bb,每批水果总数mm

输出描述

输出nn行,每行对应分配的方式数量,结果对10^5105取模

如果无法满足要求,输出empty

样例1

输入复制

1 2 3 3 12

输出

1

提示

样例解释:
由于标准值为2,普通销售商能拿到的水果只能为1,VIP销售商至少为3,所以需要的水果数至少为:3 \times 3+3 \times 1=123×3+3×1=12个,水果数量恰好为12,故分配方式只能为3 3 3 1 1 1共1种分配方式。

数据范围:

测试点nnkkaabbmm
1,21,20<n\leq 30<n≤300a\leq3a≤300m\leq100m≤100
330<n\leq 100<n≤100 \leq k < 100≤k<10a\leq3a≤300m\leq100m≤100
440<n\leq 100<n≤100 \leq k < 100≤k<10a\leq3a≤3b\leq3b≤3m\leq100m≤100
5,65,60<n\leq 100<n≤100 \leq k < 1000≤k<100a\leq3a≤3b\leq3b≤3m\leq100m≤100
7,87,80<n\leq 1000<n≤1000 \leq k < 1000≤k<100a\leq10a≤10b\leq10b≤10m\leq100m≤100
9,109,100<n<1000<n<1000 \leq k < 1000≤k<1000<a<1000<a<1000\leq b<1000≤b<100m\leq10000m≤10000

思路分析

     本题只需要求出所有方案数即可,想求出所有方案数,我们得先确定一个范围,在该范围内进行求解。

这个范围有两种办法可取,第一种是从VIP商户的最小需求开始,并且保证每个普通商户还能够分到至少1个苹果;第二种是从普通商户的最小需求开始,并且保证每个VIP还能够分到至少k+1个苹果。

好了,现在我们有范围了,那我们怎么求方案数呢?

我们可以定义两个表:dp1和dp2,分别表示i个VIP商户分j个水果的分法个数和i个普通商户分j个水果的分法个数,只需要把这两个表填好,在循环里把两个情况数相乘就可以得到当前方案数。

     定义ans=0,把每个方案数都加到ans里,就求出总方案数了。

     是不是很简单?但问题来了,我们怎么填dp1和dp2这两个表呢?这就要用到动态规划了。

     想要进行动态规划,首先得确定转移方程。

     我们先考虑VIP商户。如果我们多了一个VIP商户,那肯定得给他至少(k+1)个苹果。多加一个人,也就意味着要多分出去k+1个苹果,所以状态转移方程为:dp1[i][j]=dp1[i-1][j-(k+1)];

     我们在考虑普通商户。如果我们多了一个普通商户,那至少也得个他1个苹果。多加一个人,也就意味着要多分出去1个苹果,所以状态转移方程为:dp2[i][j]=dp2[i-1][j-1];

     但是注意,普通商户因为有苹果数量上限(不能超过k-1),所以要单独进行判断:如果可分的的水果数量j大于等于k,那么需要考虑新增加的商户分的水果必须小于k,状态转移方程也就出来了:dp2[i][j]=dp2[i-1][j-(k-1)];

     知道了这些,本题就可以迎刃而解了。

 参考代码

ac代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e5;//整型取模数字
int dp1[105][10005];//dp1[i][j]:i个VIP分j个水果,有多少种分法
int dp2[105][10005];//dp2[i][j]:i个普通商户分j个水果,有多少种分法
int sum1[105][10005],sum2[105][10005];//完成前缀和求和
int main()
{
    dp1[0][0]=1;
    dp2[0][0]=1;
    int T,k;//水果批次数、标准值
    cin>>T>>k;
    for(int j=0; j<=10000; j++)//初始化,前缀和全部初始化为1
    {
        sum1[0][j]=sum2[0][j]=1;
    }
    //前缀和计算部分
    for(int i=1; i<=100; i++)//1个vip商户到100个vip商户
    {
        for(int j=1; j<=10000; j++)//水果限制最大到1万
        {
            if(j>k)
            {
                dp1[i][j]=sum1[i-1][j-(k+1)];//分给vip商户的
            }
            if(j<k)
            {
                dp2[i][j]=sum2[i-1][j-1];//分给普通商户的
            }
            else
            {
                dp2[i][j]=(sum2[i-1][j-1]-sum2[i-1][j-k])%mod;
            }
            sum1[i][j]=(sum1[i][j-1]+dp1[i][j])%mod;//求前缀和(sum1的上一个+当前dp1的值,注意取模)
            sum2[i][j]=(sum2[i][j-1]+dp2[i][j])%mod;//求前缀和(sum2的上一个+当前dp2的值,注意取模)
        }
    }
    while(T--)//输出部分
    {
        int a,b,m;
        cin>>a>>b>>m;
        if(a*(k+1)+b>m)//如果m连vip用户按照最小量+普通用户的最小量都满足不了
        {
            cout<<"empty\n";
            continue;
        }
        ll ans=0;
        for(int i=a*(k+1); m-i>=b; i++)//开始:从vip商户最小值;判断条件:起码得能够给普通商户分一个
        {
            ans+=(ll)dp1[a][i]*dp2[b][m-i];//vip商户的情况数*普通商户的情况数,由于长度可能会很大,所以开头加上(ll)
            ans%=mod;//取模
        }
        cout<<(ans+mod)%mod<<endl;//输出
    }
    return 0;
}

我在比赛时这道题就没做,觉得有点儿难。

总结

这道题考的动态规划,需要注意取模和填表过程,这里易错。


第四题

题目名称:控制土地

时间限制:1000ms 内存限制:256MB 提交通过率:1%(这通过率我也是醉了)

个人难度评级:★★★★★

题目描述

法师小图灵在学习用魔法控制土地,初始的土地高度有高有低。小图灵想把所有土地的海拔高度都修改成 hh 。

世界上共有 nn 块土地,它们连成了一个树形结构。现在知道每块土地的高度,并且连在一起的土地可以同时修改,小图灵可以一次给一些连在一起的土地升高 11 或者降低 11 ,每次修改都必须包含小图灵所在的 11 号土地。

问最少需要多少步,小图灵就可以把土地的海拔高度都修改为 hh 。

输入描述

第一行包含两个整数 n,hn,h,表示土地的数量和目标海拔高度。

接下来的 n-1n−1 行,每行两个整数 u,vu,v,表示第 uu 块土地和第 vv 块土地是连在一起的,保证所有的土地连成一棵树。

最后一行包含 nn 个整数 a_iai​,用空格隔开,表示第 ii 块土地当前的海拔高度。

输出描述

一个整数,表示最少需要的操作次数。

样例1

输入复制

7 0 1 2 1 3 2 4 2 5 3 6 3 7 0 1 1 2 2 2 3

输出

6

提示

样例解释:
第1、2次把所有的土地下降1,
第3次把1,3,7号土地下降1,
第4次把1,2,3号土地上升1,
第5次把1,3号土地上升1,
第6次把1号土地上升1

对于100%的数据, 1 \le n \le 10^5,-10^9 \le h \le 10^9,-10^9 \le a_i \le 10^91≤n≤105,−109≤h≤109,−109≤ai​≤109

测试点编号约定
1~2对于每条边 u+1 = vu+1=v,a_iai​ 单调上升或单调下降,且所有的 a_i > hai​>h
3~4对于每条边 u+1 = vu+1=v
5~6h-1 \le a_i \le h+1h−1≤ai​≤h+1
7~10

思路分析

本题是最难的一道,考验深度优先搜索能力。

主要解题方法就是下面三条:

·子节点一定比父节点优先变为h

·每一层子节点中只需要找到需要增加和减少的最大值即可

·把子节点的贡献累积到父节点上,最后直到跟节点

参考代码

ac代码:

#include <bits/stdc++.h>
using namespace std;
const int N=100005;
int n,h,cnt=0,head[N];
long long dp[N][2],a[N];
struct land{//前向星算法
	int to,next;
}e[2*N];
void addedge(int u,int v)//初始化land
{
	e[++cnt].to=v;
	e[cnt].next=head[u];
	head[u]=cnt;
}
void dfs(int u,int fa)
{
	for(int i=head[u]; i; i=e[i].next)//遍历整个u的子节点
	{
		int v=e[i].to;
		if(v==fa)//由于是双向循环,所以遇到重复就continue
		{
			continue;
		}
		dfs(v,u);
		dp[u][0]=max(dp[u][0],dp[v][0]);//找到需要增加的最大值
		dp[u][1]=max(dp[u][1],dp[v][1]);//找到需要减少的最大值
	}
	a[u]=a[u]+dp[u][0]-dp[u][1];//a[u]等于当前的a[u]加上需要增加的次数减去需要减少的次数
	if(a[u]>0)//大于0时
	{
		dp[u][1]+=a[u];//我们认为它需要减少,所以dp[u][1]加上a[u]
	}
	else//小于0时
	{
		dp[u][0]-=a[u];//我们认为它需要增加,所以dp[u][0]减去a[u]
	}
}
int main()
{
	cin>>n>>h;
	for(int i=1; i<n; i++)
	{
		int u,v;
		cin>>u>>v;
		addedge(u,v);//调用前向星
		addedge(v,u);//调用前向星
	}
	for(int i=1; i<=n; i++)
	{
		cin>>a[i];
		a[i]-=h;//修改为a[i]与h的差,这样只要把a全部清零即可
	}
	dfs(1,1);
	cout<<dp[1][0]+dp[1][1]<<'\n';//dp[1][0]表示根节点为1,需要增加的次数;dp[1][1]表示根节点为1,需要减少的次数
	return 0;
}

总结

本题考查深搜,需要注意递归调用。


大总结

总而言之,后三道题都是有难度的,要保证能拿到的分都拿到!

比赛时要保持沉稳,时刻告诉自己可以做对;时间给的很充裕,所以有你思考的时间。建议准备好纸笔,方便打草稿。

功夫都在平时,考前临时抱佛脚是没用的。建议大家平时多多刷题,多多积累、总结经验教训,也可以让自己取得好成绩!

好啦,以上就是本次的内容,感谢大家的阅读,也祝大家在以后的比赛中取得优异的成绩!


  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值