总结
CSDN竞赛第五期。总的来说题目质量要比上一期高点,题目难度不是很大,但是要想ac也是不容易的。
题目列表
1.寻因找组
题目描述
寻找因子个数为n的最小整数x,其中n<=1000。
分析
题目顺序设置的可能不是很合理,第一道题难度差不多接近这次竞赛的压轴题了,而且属于语言不友好型,题目答案可能很大,远超long long的存储范围,也就是使用c++实现需要手动实现高精度,而使用python实现则可以直接进行计算不用考虑大数,因此,很多大佬的第一题都是使用python实现的。
本题主要考察算术基本定理,考试时推导有点小问题,只通过了百分之九十的用例,考试报告里显示通过了百分之八十的用例,赛后完善了下应该是可以通过所有用例的。由算术基本定理知,对任意的正整数x有:x = p1n1 * p2n2 * p3n3 *…*pknk。其中p1到pk是x的质因子。因此x的因子个数一共有(n1 + 1)(n2 + 1)…(nk + 1)个,这是根据乘法原理来的,每个质因子可以选0到ni个,很容易推出这个结论。
因此,本题就转化为了对每个质因子数量的枚举,使得最终质因子数量加1的积等于n,要想构成的数最小,则较小的质因数应该尽可能的多出现。在求解本题前,先考虑两个问题,第一,最多有多少个不同的质数可以构成1000个因子的整数,假设每个质数只出现一次,10个质数就有210 = 1024个因子了,所以最多只用考虑10个质数,我们在枚举x的质因子时也只需要枚举最小的10个质因子即可。第二个问题是本题的x可能有多大?并不是n越大x就越大的,1000个因子构成的整数没有多大,但是像997个因子构成的整数就相当大了,因为没法对997进行因数分解,由997个因子构成的最小整数就是2996,而这个数是接近10300级别的。
考虑到本题的答案可能很大,使用两种方式进行处理。首先是取对数,就算x很大,logx还是在可接受的范围内的,logx = n1logp1 + n2logp2 +… +nklogpk,每枚举一个新的质因子,只需要加上nklogk即可。另外是最终的结果要输出,所以可以存储每个质因子出现的次数,最后使用高精度乘法来求解最后的结果。
简要的描述下本题的求解过程。
用prime存储前10个质数,t存储n的因子,由于(n1 + 1)(n2 + 1)…(nk + 1) = n,所以每个质因子的数量一定是n的因子减去1。在枚举过程中,一旦此时累乘的因子数超过了n,或者不是n的因子,代表继续累乘下去也不可能得到n,应该应该舍弃。
代码
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <cmath>
using namespace std;
int prime[12] = {2,3,5,7,11,13,17,19,23,29};
vector<int> t,temp,cnt;
double ans = 1e9;
int n;
void dfs(int u,int muls,double res) {
if(muls == n) {//因子个数达到n
if(res < ans) {//logx更新
ans = res;
cnt = temp;
}
return;
}
if(u >= 10 || res > ans) return;
for(int i = 0;i < t.size();i++) {
int s = t[i] * muls;
if(s > n || n % s) continue;//剪枝
temp.push_back(t[i] - 1);
dfs(u + 1,s,res + (t[i] - 1) * log(prime[u]));
temp.pop_back();
}
}
vector<int> mul(vector<int> &a, int b) {
vector<int> c;
int t = 0;
for(int i = 0;i < a.size();i++) {
t += a[i] * b;
c.push_back(t % 10);
t /= 10;
}
if(t) c.push_back(t);
return c;
}
int main() {
cin>>n;
for(int i = n;i >= 1;i--) {
if(n % i == 0) t.push_back(i);
}
dfs(0,1,0);
vector<int> res = {1};
for(int i = 0;i < cnt.size();i++) {
if(cnt[i]) {
while(cnt[i]--) res = mul(res,prime[i]);
}
}
for(int i = res.size() - 1;i >= 0;i--) cout<<res[i];
cout<<endl;
return 0;
}
2.通货膨胀-x国货币
题目描述
X国发行货币最高面额为n。 次高面额为n的因子。 以此类推。 X国最多发行多少种货币。
分析
签到题,不停的枚举n的最大因子,然后将最大因子赋给n,直至n为1为止,统计下枚举的个数即可。
代码
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
int get(int n) {
for(int i = n / 2;i >= 1;i--) {
if(n % i == 0) return i;
}
return 1;
}
int solution(int n){
if(n == 1) return 1;
int res = 2;
while(get(n) != 1) {
n = get(n);
res++;
}
return res;
}
int main() {
int n;
std::cin>>n;
int result = solution(n);
std::cout<<result<<std::endl;
return 0;
}
3.莫名其妙的键盘
题目描述
有一个神奇的键盘,你可以用它输入a到z的字符,然而每当你输入一个元音字母(a,e,i,o,u其中之一)的时候,已输入的字
符串会发生一次反转! 比方说,当前输入了tw,此时再输入一个o,此时屏幕上的字符串two会反转成owt。 现给出一个
字符串,若用该键盘输入,有多少种方法可以得到?
分析
本题考试时想复杂了,没有通过几个用例,考试时还考虑到相同元音字母和不同元音字母的输入此时的不同了。实际只需要通过减而治之来求解即可,思维难度要比第一题大。
我们不需要关心一共有多少种方法得到。只需要关心最后一个输入的字符是什么。
如果输入字符串的最后一个字符是元音字母,如果是最后输入的,显然会翻转到开头去,因此只可能是该元音此前在开头,最后又输出一个元音字母,才使得开头的元音字母跑到末尾的,这种情况下如果输入的开头不是元音,则不存在合法的输入序列。因此只需要考虑输入字符串首尾都是元音的情况,此时最后一个输入的字符是首字符。比如abco,输入a前的字符串是ocb。
如果输入字符串的最后一个字符不是元音,那么就考虑开头字母了,比较复杂的是首字母是元音的情况。比如abcd,最后一个输入的字符可能是a或者d,如果最后输入的是a,意味着输入a之前的字符串就是dcb;如果最后输入的是d,说明之前的字符串就是abc。
如果输入字符的首尾字符都不是元音,那么最后一个输入的字符就是末字符。
本题的难度在于分类讨论,分类正确了,代码实现就很简单了。
代码
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>
using namespace std;
bool judge(char c) {
char s[6] = "aeiou";
for(int i = 0;i < 5;i++) {
if(c == s[i]) return true;
}
return false;
}
int dfs(string &s) {
int n = s.size();
if(n == 1) return 1;
if(judge(s[n-1])) {
if(!judge(s[0])) return 0;
string t;
for(int i = n - 1;i;i--) t += s[i];
return dfs(t);
}
string s1 = s.substr(0,n-1);
if(judge(s[0])) {
string t;
for(int i = n - 1;i;i--) t += s[i];
return dfs(s1) + dfs(t);
}
return dfs(s1);
}
int main() {
string s;
cin>>s;
cout<<dfs(s)<<endl;
return 0;
}
4.三而竭
题目描述
一鼓作气再而衰三而竭。 小艺总是喜欢把任务分开做。 小艺接到一个任务,任务的总任务量是n。 第一天小艺能完成x份
任务。 第二天能完成x/k。 。。。 第t天能完成x/(k^(t-1))。 小艺想知道自己第一天至少完成多少才能完成最后的任务。
分析
考察二分的基本应用。总任务量是n,如果小艺第一天就完成n份,那么任务一定能够完成,所以第一天完成的任务量是在1到n之间的,并且问题的解具有单调性,也就是说,如果第一天完成mid份任务能够完成最终的任务,那么第一天完成的比mid多也一定可以完成最后的任务,所以对答案进行二分即可。
代码
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
bool check(int x,int n,int k) {
int cnt = 0;
while(cnt < n) {
cnt += x;
x /= k;
if(cnt < n && !x) return false;
}
return true;
}
int solution(std::vector<int>& vec){
int n = vec[0],k = vec[1];
int l = 1,r = n;
while(l < r) {
int mid = (l + r) >> 1;
if(check(mid,n,k)) r = mid;
else l = mid + 1;
}
return l;
}
int main() {
std::vector<int> vec;
std::string line_0, token_0;
getline(std::cin >> std::ws,line_0);
std::stringstream tokens_0(line_0);
while(std::getline(tokens_0, token_0, ' ')){
vec.push_back(std::stoi(token_0));
}
int result = solution(vec);
std::cout<<result<<std::endl;
return 0;
}