目录
1,题目描述
Sample Input 1:
169 5 2
Sample Output 1:
169 = 6^2 + 6^2 + 6^2 + 6^2 + 5^2
Sample Input 2:
169 167 3
Sample Output 2:
Impossible
题目大意
将一个数N分解成K个因子的P次方之和,要求当结果不唯一时,取因子之和最大的,仍不唯一,则将序列按照从大到小排序,选择较大序列(从第一个数往后,直到当前位置的数组A中的数大于数组B中的数;
不存在这样的分解方法时,输出Impossible;
2,思路
参考大神的解法:@日沉云起【pat甲级1103. Integer Factorization (30)】
(解题过程的第二搏,第三搏中的思路均可通过测试,这里仅详细介绍最简解法)
数据结构
- vector<int> tem:暂时存放结果序列;
- vector<int> ans:结果序列;
- vector<int> powValue:存储所有不超过N的n^P中的n;
- DFS函数参数:start指向powValue的特定位置,dep深度(个数),facSum因子之和,powSum指数之和;
算法
- 对不超过N的n^P进行打表,并把结果存储到powValue中,DFS遍历时针对此表进行:
- DFS设计:出口(dep >= K || powSum >= N,若满足dep == K && powSum == N && facSum > maxFacSum则更新ans);遍历(选择当前的start;不选择当前的start,而是更小的数)注意入栈/出栈的时机,以及递归的参数;
- 函数调用及输出:
3,AC代码
(即第三搏中的代码)
#include<bits/stdc++.h>
using namespace std;
int N, K, P;//N给出的数 K因子个数 P因子的指数
vector<int> tem, ans, powValue;
int maxFacSum = 0;
void initPow(){ //下标对应因子 值对应因子的P次方
powValue.push_back(0);
while(powValue.back() < N)
powValue.push_back(pow(powValue.size(), P)); // !!!妙啊
}
void dfs(int start, int dep, int facSum, int powSum){ //start指向powValue的特定位置 dep深度 facSum因子之和 powSum指数之和
if(dep >= K || powSum >= N){
if(dep == K && powSum == N && facSum > maxFacSum){
maxFacSum = facSum;
ans = tem;
}
return;
}
if(start >= 1){
tem.push_back(start);
dfs(start, dep+1, facSum+start, powSum+powValue[start]); //powValue[start]即start的P次方
tem.pop_back();
dfs(start-1, dep, facSum, powSum); //未选择当前的start 而是选择更小的数
}
}
int main(){
#ifdef ONLINE_JUDGE
#else
freopen("1.txt", "r", stdin);
#endif // ONLINE_JUDGE
scanf("%d %d %d\n", &N, &K, &P);
initPow();
dfs(powValue.size()-1, 0, 0, 0); //从大到小开始选择
if(ans.size() == K){
printf("%d = %d^%d", N, ans[0], P);
for(int i = 1; i < ans.size(); i++)
printf(" + %d^%d", ans[i], P);
}else
printf("Impossible");
return 0;
}
4,解题过程
第一搏
想到了DFS,但是连样例都没运行出来。。。
#include<bits/stdc++.h>
using namespace std;
int N, K, P;//N给出的数 K因子个数 P因子的指数
vector<int> tem, ans;
bool cmp2(int a, int b) {return a > b;}
bool cmp1(vector<int>& A, vector<int>& B){
int sumA = 0, sumB = 0;
if(B.size() == 0) return true;
for(int i = 0; i < K; i++){
sumA += A[i];
sumB += B[i];
}
if(sumA != sumB) return sumA > sumB;
sort(A.begin(), A.end(), cmp2);
sort(B.begin(), B.end(), cmp2);
for(int i = 0; i < K; i++)
if(A[i] != B[i])
return A[i] > B[i];
}
void dfs(int start, int dep, int sum){
tem.push_back(start);
sum += (int)pow(start, P);
// if(sum > N){
// tem.pop_back();
// return;
// }
if(dep >= K){
//printf("YES1\n");
if(sum == N){
for(int i = 0; i < tem.size(); i++)
printf(" %d", tem[i]);
printf("\n");
if(cmp1(tem, ans))
ans = tem;
}
//printf("YES2\n");
tem.pop_back();
return;
}
for(int i = start; i <= 20; i++){//P最小为2 N最大为400 20^2=400
dfs(i, dep+1, sum);
//tem.pop_back();
}
tem.pop_back();
}
int main(){
#ifdef ONLINE_JUDGE
#else
freopen("1.txt", "r", stdin);
#endif // ONLINE_JUDGE
scanf("%d %d %d\n", &N, &K, &P);
for(int i = 1; i <= 20; i++)
dfs(i, 1, 0);
//cout<<ans.size()<<endl;
// for(int i = 0; i < ans.size(); i++)
// printf(" %d", ans[i]);
return 0;
}
看着自己写的代码,都感觉时间复杂度要炸开了。。。
第二搏
向大神求助,,,
不愧是大神,逻辑简单但十分准确到位:
#include<bits/stdc++.h>
using namespace std;
int N, K, P;//N给出的数 K因子个数 P因子的指数
vector<int> tem, ans;
int maxFacSum = 0;
void dfs(int start, int dep, int facSum, int powSum){//start起点 dep深度 facSum因子之和 powSum指数之和
if(dep >= K || powSum >= N){
if(dep == K && powSum == N && facSum > maxFacSum){
maxFacSum = facSum;
ans = tem;
}
return;
}
if(start >= 1){
tem.push_back(start);
dfs(start, dep+1, facSum+start, powSum+pow(start*1.0, P*1.0));//选择这个start !!!这里的pow函数不要强制转换为int 会出错
tem.pop_back();
dfs(start-1, dep, facSum, powSum);//未选择当前的start 而是选择更小的数
}
}
int main(){
#ifdef ONLINE_JUDGE
#else
freopen("1.txt", "r", stdin);
#endif // ONLINE_JUDGE
scanf("%d %d %d\n", &N, &K, &P);
dfs((int)pow(N*1.0, 1.0/P)+1, 0, 0, 0);//从P次根号下N从大到小进行选择; +1保证强制转换后不会漏掉数;
if(ans.size() == K){
printf("%d = %d^%d", N, ans[0], P);
for(int i = 1; i < ans.size(); i++)
printf(" + %d^%d", ans[i], P);
}else
printf("Impossible");
return 0;
}
对一些必要的地方进行了剪枝:
- 对因子的最大值进行了限制:
- 对因子的最大个数dep,和指数之和powSum进行剪枝:
精妙之处:
- 使递归从最大的数开始,这样可以避免当因子之和相等时,一个个比较序列中的数字;
- 适当剪枝;
有种子弹从耳朵👂边飞过的感觉。。。Σ(っ °Д °;)っ
第三搏
打表法!
对不超过N的n^P进行打表,并把结果存储到powValue中,从而避免重复计算数字i的P次方。
每次从powValue中取值:下标即因子,值即因子对应的P次方;
#include<bits/stdc++.h>
using namespace std;
int N, K, P;//N给出的数 K因子个数 P因子的指数
vector<int> tem, ans, powValue;
int maxFacSum = 0;
void initPow(){ //下标对应因子 值对应因子的P次方
powValue.push_back(0);
while(powValue.back() < N)
powValue.push_back(pow(powValue.size(), P)); // !!!妙啊
}
void dfs(int start, int dep, int facSum, int powSum){ //start指向powValue的特定位置 dep深度 facSum因子之和 powSum指数之和
if(dep >= K || powSum >= N){
if(dep == K && powSum == N && facSum > maxFacSum){
maxFacSum = facSum;
ans = tem;
}
return;
}
if(start >= 1){
tem.push_back(start);
dfs(start, dep+1, facSum+start, powSum+powValue[start]); //powValue[start]即start的P次方
tem.pop_back();
dfs(start-1, dep, facSum, powSum); //未选择当前的start 而是选择更小的数
}
}
int main(){
#ifdef ONLINE_JUDGE
#else
freopen("1.txt", "r", stdin);
#endif // ONLINE_JUDGE
scanf("%d %d %d\n", &N, &K, &P);
initPow();
dfs(powValue.size()-1, 0, 0, 0); //从大到小开始选择
if(ans.size() == K){
printf("%d = %d^%d", N, ans[0], P);
for(int i = 1; i < ans.size(); i++)
printf(" + %d^%d", ans[i], P);
}else
printf("Impossible");
return 0;
}
注意:在某些编译环境中,pow函数的取值可能会产生误差。比如代码在codeBlock上运行时,针对样例输出(第二搏中的代码,也出现了这样的问题,但是把强制转换去掉后,莫名其妙就OK了)
但是,在PTA平台上提交,没有任何问题:
贼稳!o(* ̄▽ ̄*)ブ