文章目录
这应该是站内相对详细的解题报告吧。
转载思路请@本人。
感想
关于自己
这一次比赛感觉比上一期质量好了一些,难度也削弱了很多(对我来说)。
但是还是没有发挥最佳水平,毕竟有些地方还是需要深入研究。
第一题比赛结束后才发现要高精度
Q
A
Q
QAQ
QAQ。
关于平台
这一次的做题网站听说出现了问题,不过本人并没有遇见。
相对于上一次比赛,题目的测试点放的也比较正确(不像上一次都不知道错误在哪)。
希望平台人员能更加努力,研究出高质量的题目,
同时也希望能避免样例数据等的错误或者题目描述的模糊不清(例如不知道数据的类型是整型还是其他)。
-
对于第一次来参加本比赛的人:
可能会对内置的代码模板产生疑惑,不知道能否删除模板来打自己的代码;
也会对比赛的分制不太了解,不知道可以看测试点通过率来打部分分(就像我之前放弃打部分分,导致排名大跌)。
针对上述情况,可以增加一些关于代码模板和计分规则上的说明,这样就能减少出现上述问题的几率。 -
对于经常打各种比赛的人:
也许会对比赛时的在线IDE不习惯(或者不会用);
或者因为题目没有给数据范围/类型二导致无法计算出算法正确的时间/空间复杂度。
针对上述情况,我个人的建议是优化IDE,完善题目中的各类数据范围/类型。
顺便提一个问题:在每日一练中有些选择题的答案是错误的,这个会严重拉低用户的正确率,希望管理员能尽快修复。
第一题 寻因找祖(难度:中等)
题目描述
寻找因子个数为 n ( n ≤ 1000 ) n(n\leq1000) n(n≤1000)的最小整数 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
分情况讨论:
- 当 n n n为素数时,直接输出 2 n 2^n 2n即可。
- 当
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,j−1,1+=fi,j,0
f
i
,
j
−
1
,
1
+
=
f
i
,
j
,
1
f_{i,j-1,1}+=f_{i,j,1}
fi,j−1,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/kt−1。 小艺想知道自己第一天至少完成多少才能完成最后的任务。
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}
kt−1,所以这个可以用等比数列公式解决。
直接套用公式即可。如果不会可以查百度。