2024年9月电子学会等级考试五级第三题——整数分解

题目

3、整数分解
正整数 N 的 K-P 分解是指将 N 写成 K 个正整数的 P 次方的和。本题就请你对任意给定的正整数 N、K、P,写出 N 的 K-P 分解。
时间限制:8000
内存限制:262144
输入
输入在一行给出 3 个正整数 N (≤ 400)、K (≤ N)、P (1 < P ≤ 7),以空格分隔。
输出
如果存在解,则按下列格式输出: N = n[1]^P + … n[K]^P 其中 n[i] (i = 1, …, K) 是第 i 个分解因子。所有的分解因子要按非增顺序输出。 注意:解可能是不唯一的。例如 169 的 5-2 分解就存在 9 个解,如 12^2 + 4^2 + 2^2 + 2^2 + 1^2 或 11^2 + 6^2 + 2^2 + 2^2 + 2^2 等等。你必须输出分解因子和最大的那个解。如果还不唯一,则输出具有最大的分解因子序列的解 —— 我们称序列 { a1, a2, … , aK } 比序列 { b1, b2, … , bK } 大,如果存在 1 ≤ L ≤ K 使得 ai=bi 对于 i < L 成立,并且有 aL > bL。 如果解不存在,则输出 Impossible
样例输入
样例#1:
169 5 2

样例#2:
169 167 3
样例输出
样例#1:
169 = 6^2 + 6^2 + 6^2 + 6^2 + 5^2

样例#2:
Impossible

代码

#include <bits/stdc++.h>
#include <windows.h>
using namespace std;
struct node{
	int num,//因数 
		x;//底数
	bool operator<(const node &b)const{return num>b.num;}
	//重载<比较运算符,定义比较规则。这里是反序,大了才小(sort默认<升序,这里改成降序) 
	//第一个const是常量引用,第二个是不修改该成员变量 
};
int n,k,p,ans=0;
vector<node> ans_v,v;//最终因数和深搜时因数 
void view(string s,const vector<node> &v){
	cout<<s<<endl;
	for(auto x=v.begin();x!=v.end();x++)
	if(x==v.begin())cout<<n<<":"<<x->x<<"^"<<p;
	else cout<<"+"<<x->x<<"^"<<p;
	cout<<endl;
	//Sleep(3000);
}
//bool go(剩因数和,剩几个数,上个因数){
void go(int he,int left,int num){
	if(he<0||left<0||he>0&&left==0||he==0&&left){//剪枝 
		cout<<endl;return;
	}
	cout<<"出发状态:"<<he<<"\t"<<left<<endl;
	if(he==0&&left==0){//到达目标状态,凑够了 
		view("again",v);
		int sum=0;
		sort(v.begin(),v.end());
		for(auto i=v.begin();i!=v.end();i++)sum+=i->x;//因数底数和 
		cout<<sum<<"\t"<<ans<<endl;
		if(sum<ans)return;//因数底数和小了 
		else if(sum==ans){//一样则用字典序高 
			bool k=1;
			for(vector<node>::iterator i=v.begin(),j=ans_v.begin();i!=v.end();i++,j++)
			if(i->x>j->x)break;
			else if(i->x<j->x){k=0;break;}
			if(k)ans_v=v;
			else return;
		}else{
			ans_v=v,ans=sum;
			view("ok",v);
		}
	}
	for(int x=he;x>0;x--){//遍历因数 
		int d=pow(x,1.0/p); //开方计算求底数 
		if(pow(d,p)!=x)continue;//不能开方 
		if(x>num)continue;//剪枝重复情况,10+5和5+10是同种情况,这里只要降序 
		if(x*left<he)break;//剪枝凑不够情况,剩的全是最大因数都不够凑目标数 
		cout<<"分出"<<x<<";";
		v2.push_back({x,d});
		go(he-x,left-1,x);//继续对剩余数拆解因数 
		v2.pop_back();//回溯 
	}
}
int main(){
	freopen("data.cpp","r",stdin);
	cin>>n>>k>>p;
	go(n,k,n);
	if(ans==0)cout<<"imposibel";
	else {
		for(auto i=ans_v.begin();i!=ans_v.end();i++){
			if(i==ans_v.begin())cout<<n<<"="<<i->x<<"^"<<p;
			else cout<<"+"<<i->x<<"^"<<p;
		}
	}
	return 0;
}

分析

  • 是k个数的和,
  • 而且每个数都是p次幂
  • 各因子的和最大
  • 和相同,选择字典序最大
  • 指数运算(乘方运算) 2^4=16 底数2, 指数4, 幂是16
  • 开放运算 pow(16,-1.0/4) 底数16,指数1.0/4 根是2
  • 剪枝条件:如果当前因数乘以剩余个数少于剩余总和,则跳过该值。

结果

会有多组解,
留下因数底数和最大的
一样大的,留下字典序高的
在这里插入图片描述

小结

分层解决问题
先解决因数和的事情
再解决开方的问题
然后是因数和
后是字典序
该问题状态都,用BFS需要空间大

修正

1.预处理找到所有小于n的p次方数,存在数组中。
2.函数增加和参数,在递归时完成和计算,免于找最优解时单独计算。

#include <bits/stdc++.h>
using namespace std;
int n,//该数 
	k,//K个因数
	p,//p次方
	m,//小于n的p次方数个数 
	maxhe;
struct node{
	int x,//底数 
		d;//幂 
	bool operator<(const node& b)const{return x>b.x;}
}d[400];
vector<node> v1,v2;
void view(int he){
	cout<<he<<endl;
	for(vector<node>::iterator i=v2.begin();i!=v2.end();i++)cout<<i->x<<"\t";
	cout<<endl;
}
//参数1=要拆分什么数,参数2=还剩几个数,参数3=上次拆分的最小数 ,因数和 
void go(int he,int k,int x,int sum){
	bool xk=0; 
	if(he<0||k<0)return;//越界 
	if(k==0&&he==0){//凑出来了 
		view(sum);
		if(sum<maxhe)return;//要最大因数和 
		bool ok=1;
		if(sum==maxhe){//相等情况,要字典序高的组
			sort(v1.begin(),v1.end());
			for(vector<node>::iterator i=v1.begin(),j=v2.begin();i!=v1.end()&&j!=v2.end();i++,j++)
			if(i->x<j->x){ok=0;break;}//小了,不要 
			else if(i->x>j->x)break;//大了,可以要 
		}
		if(ok){//可以 
			xk=1;
			cout<<"更优解\n"; 
			maxhe=sum;v2=v1;
			view(maxhe);
			return;//更新 
		}else return;
	}
	for(int i=x;i<m;i++){
		if(d[i].d*k<he)continue; 
		v1.push_back(d[i]);
		go(he-d[i].d,k-1,i,sum+d[i].x);
		v1.pop_back();
	}
}
int main(){
	freopen("data.cpp","r",stdin);
	cin>>n>>k>>p;
	for(int i=n;i>0;i--){
		int x=pow(i,1.0/p);
		if(pow(x,p)==i){
			d[m++]=node{x,i};
			cout<<m-1<<"\t"<<d[m-1].x<<"\t"<<d[m-1].d<<endl;
		}
	}
	go(n,k,0,0);
	if(maxhe>0){
		sort(v2.begin(),v2.end());
		cout<<n<<"=";
		for(vector<node>::iterator i=v2.begin();i!=v2.end();i++)
			if(i==v2.begin())cout<<i->x<<"^"<<p;
			else cout<<"+"<<i->x<<"^"<<p;
	}else cout<<"impossible";
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值