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


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

    感想

    关于自己

    这一次比赛感觉比上一期质量好了一些,难度也削弱了很多(对我来说)。
    但是还是没有发挥最佳水平,毕竟有些地方还是需要深入研究。
    第一题比赛结束后才发现要高精度 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
    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;
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    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
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

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

    题目描述

    有一个神奇的键盘,你可以用它输入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
    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    100分做法2

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

  • 相关阅读:
    回归算法详解
    成都睿趣科技:现在开一家抖音小店还来得及吗
    使用 Python 实现 Excel 自动化
    msfconsole数据库连接不了的问题【已解决】
    解决vs移植编译MFC工程爆红
    vue中列表实现漏出即曝光
    Dart学习——函数、类
    Java 基于 SpringBoot 的在线学习平台
    【Spring面试】一、SpringBoot启动优化与Spring IoC
    处理游戏提示找不到steam_api64.dll丢失的方法
  • 原文地址:https://blog.csdn.net/qq_42989972/article/details/126788471