【CSDN竞赛】第五期解题报告

18 篇文章 0 订阅
17 篇文章 0 订阅

这应该是站内相对详细的解题报告吧。
转载思路请@本人。

感想

关于自己

这一次比赛感觉比上一期质量好了一些,难度也削弱了很多(对我来说)。
但是还是没有发挥最佳水平,毕竟有些地方还是需要深入研究。
第一题比赛结束后才发现要高精度 Q A Q QAQ QAQ

关于平台

这一次的做题网站听说出现了问题,不过本人并没有遇见。
相对于上一次比赛,题目的测试点放的也比较正确(不像上一次都不知道错误在哪)。
希望平台人员能更加努力,研究出高质量的题目,
同时也希望能避免样例数据等的错误或者题目描述的模糊不清(例如不知道数据的类型是整型还是其他)。

  • 对于第一次来参加本比赛的人:
    可能会对内置的代码模板产生疑惑,不知道能否删除模板来打自己的代码;
    也会对比赛的分制不太了解,不知道可以看测试点通过率来打部分分(就像我之前放弃打部分分,导致排名大跌)。
    针对上述情况,可以增加一些关于代码模板和计分规则上的说明,这样就能减少出现上述问题的几率。

  • 对于经常打各种比赛的人:
    也许会对比赛时的在线IDE不习惯(或者不会用);
    或者因为题目没有给数据范围/类型二导致无法计算出算法正确的时间/空间复杂度。
    针对上述情况,我个人的建议是优化IDE,完善题目中的各类数据范围/类型。

顺便提一个问题:在每日一练中有些选择题的答案是错误的,这个会严重拉低用户的正确率,希望管理员能尽快修复。

第一题 寻因找祖(难度:中等)

题目描述

寻找因子个数为 n ( n ≤ 1000 ) n(n\leq1000) n(n1000)的最小整数 x x x.。

50分做法

对于 n n n较小的情况,我们可以从小到大枚举整数的可能性,然后计算当前枚举的数的因子个数。
如果等于 n n n就输出答案并结束程序。
代码非常简单,这里略。

70分做法

加速50分做法中求解素数和分解质因数的过程(例如欧拉筛)就可以拿到70分。

90分做法

发现当 n = 1000 n=1000 n=1000时,答案超过了暴力枚举的可行范围,会导致超时。怎么办呢?
搜索!

首先预处理出一个 1 1 1~ 20 20 20的素数表,然后枚举每一个质数的次幂。
根据 构造的数 = p 1 c 1 × p 2 c 2 × . . . × p p r i m e c p r i m e 构造的数=p_1^{c_1}×p_2^{c_2}×...×p_{prime}^{c_{prime}} 构造的数=p1c1×p2c2×...×pprimecprime 因子个数 = ( c 1 + 1 ) × ( c 2 + 1 ) × . . . × ( c p r i m e + 1 ) 因子个数=(c_1+1)×(c_2+1)×...×(c_{prime}+1) 因子个数=(c1+1)×(c2+1)×...×(cprime+1) 可以得出,
我们可以搜索时顺便计算出当前构造出的数的因子个数,然后和 n n n作比较。
如果大于 n n n就可以搜索回溯,等于就更新 a n s w e r answer answer
同样地,如果 a n s w e r answer answer小于等于当前构造出的数字,则回溯。
最后输出 a n s w e r answer answer即可。
C + + C++ C++代码如下:

#include<bits/stdc++.h>
using namespace std;
int p[105]={},pr[105]={};
long long n;
long long ans=1e17;
void dfs(int x,long long c,long long s)
{
	if(c>n)return;
	if(s>ans)return;
	if(x>p[0])
	{
		if(c==n)ans=min(ans,s);
		return;
	}
	long long k=1;
	for(long long i=0;c*(i+1)<=n&&k*s<=1e17;i++)
	{
		dfs(x+1,c*(i+1),s*k);
		k*=p[x];
	}
}
int main()
{
	pr[0]=pr[1]=1;
	for(int i=2;i<=20;i++)
	{
		if(!pr[i])
		{
			p[++p[0]]=i;
		}
		for(int j=1;j<=p[0]&&p[j]*i<=20;j++)
		{
			pr[i*p[j]]=1;
			if(i%p[j]==0)break;
		}
	}
	scanf("%lld",&n);
	dfs(1,1,1);
	printf("%lld\n",ans);
	return 0;
}
}

100分做法1

将上述代码加上高精度就行啦。

100分做法2

分情况讨论:

  1. n n n为素数时,直接输出 2 n 2^n 2n即可。
  2. n n n不为素数时,从小到大选择质数作为因子,然后随便构造次幂就行了。

第二题 通货膨胀-x国货币(难度:签到)

题目描述

X国发行货币最高面额为 n n n。 次高面额为 n n n的因子。 以此类推。 X国最多发行多少种货币。

100分做法

转化题意:求 n n n分解质因数后一共有多少个质因子相乘。
先利用筛法筛出素数,然后对 n n n质因数分解,最后输出质因子个数就行了。
C + + C++ C++代码如下:

#include<bits/stdc++.h>
using namespace std;
int p[1000005]={},pr[1000005]={},Z[1000005]={};//Z表示一个数的最小质因子
int n;
int main()
{
	scanf("%d",&n);
	pr[1]=pr[0]=1;
	for(int i=2;i<=n;i++)
	{
		if(!pr[i])p[++p[0]]=i,Z[i]=i;
		for(int j=1;j<=p[0]&&i*p[j]<=n;j++)
		{
			pr[i*p[j]]=1;
			Z[i*p[j]]=p[j];
			if(i%p[j]==0)break;
		}
	}
	int tot=1;
	while(n>1)
	{
		n/=Z[n];
		tot++;
	}
	printf("%d\n",tot);
	return 0;
}

第三题 莫名其妙的键盘(难度:简单)

题目描述

有一个神奇的键盘,你可以用它输入a到z的字符,然而每当你输入一个元音字母( a , e , i , o , u a,e,i,o,u a,e,i,o,u其中之一)的时候,已输入的字符串会发生一次反转! 比方说,当前输入了 t w tw tw,此时再输入一个 o o o,此时屏幕上的字符串 t w o two two会反转成 o w t owt owt。 现给出一个字符串,若用该键盘输入,有多少种方法可以得到?

100分做法1

经过简单的观察,一眼就能看出来:这不就是区间动态规划吗?
首先将操作反转,变成每次删除一个字符,有些情况字符串会发生反转,求最终结束状态。
我们设 f i , j , 0 / 1 f_{i,j,0/1} fi,j,0/1表示当前剩余未删除的区间为 [ i , j ] [i,j] [i,j],刚才在头/尾删除了字符,方案数是多少。
将头/尾分元音和辅音两种情况讨论,共可以得出 4 4 4条状态转移方程:
f i + 1 , j , 0 + = f i , j , 0 f_{i+1,j,0}+=f_{i,j,0} fi+1,j,0+=fi,j,0
f i , j − 1 , 1 + = f i , j , 0 f_{i,j-1,1}+=f_{i,j,0} fi,j1,1+=fi,j,0
f i , j − 1 , 1 + = f i , j , 1 f_{i,j-1,1}+=f_{i,j,1} fi,j1,1+=fi,j,1
f i + 1 , j , 0 + = f i , j , 1 f_{i+1,j,0}+=f_{i,j,1} fi+1,j,0+=fi,j,1
其中 1 , 3 1,3 1,3条是辅音, 2 , 4 2,4 2,4条是元音。
这个应该非常好理解吧(就是每次删掉头/尾,然后判断是否旋转)。
然后,我们先枚举长度,然后枚举区间,就可以轻松转移。
最后将所有长度为 1 1 1的情况答案全部加起来就可以了。
C + + C++ C++代码如下:

#include<bits/stdc++.h>
using namespace std;
long long dp[205][205][2]={},ans=0;
int str[505]={};
bool ok[505]={};
int len=0;
int main()
{
	ok['a']=ok['i']=ok['u']=ok['o']=ok['e']=1;//元音
	str[++len]=getchar();
	while(!(str[len]>=32&&str[len]<=126))str[len]=getchar();
	while(str[len]>=32&&str[len]<=126)str[++len]=getchar();//读入,可以不用这种方法
	len--;
	dp[1][len][1]=1;
	for(int t=len;t>=1;t--)
	{
		for(int i=1;i+t-1<=len;i++)
		{
			int j=i+t-1;
			if(!ok[str[i]])dp[i+1][j][0]+=dp[i][j][0];
			if(ok[str[j]])dp[i][j-1][1]+=dp[i][j][0];
			if(!ok[str[j]])dp[i][j-1][1]+=dp[i][j][1];
			if(ok[str[i]])dp[i+1][j][0]+=dp[i][j][1];
			if(t==1)ans+=dp[i][j][0]+dp[i][j][1];//累加答案
		}
	}
	printf("%lld\n",ans);
	return 0;
}

100分做法2

我不会动态规划,怎么办?
找规律!
观察到,如果一个字符串没有元音,那么只有一种方案。
如果有元音,则一定有一个元音在头/尾。
然后从一端一直删,删到一个元音后停止,反转字符串。
重复上述操作即可。
直到剩下两个元音,这时候讨论下方案数就行了(本人也没实现过)。

第四题 三而竭(难度:签到)

题目描述

一鼓作气再而衰三而竭。 小艺总是喜欢把任务分开做。 小艺接到一个任务,任务的总任务量是 n n n。 第一天小艺能完成 x x x份任务。 第二天能完成 x / k x/k x/k, 第 t t t天能完成 x / k t − 1 x/k^{t-1} x/kt1。 小艺想知道自己第一天至少完成多少才能完成最后的任务。

100分做法1

二分的板题啦~
首先二分一个答案,然后暴力算出若干天能否做完(如果做到某一天为 0 0 0就停止)。
如果能,就更新答案。
然后继续二分。
C + + C++ C++代码如下:

#include<bits/stdc++.h>
using namespace std;
long long n,ans;
long long k;
bool check(long long x)
{
	long long sum=0,N=n,X=x;
	while(X>0&&N-sum>0)
	{
		sum+=X;
		X/=k;
	}
	return N-sum<=0;
}
int main()
{
	scanf("%lld%lld",&n,&k);
	long long l=0,r=1e18;
	while(l+1<r)
	{
		long long mid=(l+r)/2;
		if(check(mid))
		{
			ans=mid;
			r=mid;
		}
		else l=mid;
	}
	printf("%lld\n",ans);
	return 0;
}

100分做法2

因为每次做的任务都会除以 k t − 1 k^{t-1} kt1,所以这个可以用等比数列公式解决。
直接套用公式即可。如果不会可以查百度。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值