CSDN竞赛5期题解

总结

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;
}
  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值