1

题目大意:

给定正整数N,K,P,将N表示为K个正整数的P次方和(递减排序),如果有多个选择底数和最大的方案,如果还有多个,选择底数序列字典序最大的序列

算法思路:

题目的思路倒是不难,常规遍历都可以想得到,但是设计到的技巧还有些,最容易想到的就是从1开始遍历数字,累加当前遍历数字的P次方和,找到相等于N的即可那么在遍历数字的时候会出现选择该数或不选择该数的情,并且可以重复选择同一个数字,很自然就想到深度优先遍历的思想。下面就大体说下深度优先遍历的基本过程:

  • 1、函数的参数有5个,分别是$factor,sum,num,N,K,P$,代表的含义分别是当前选择的因子,累计的$P$次方和,选择数字的个数,$N,K,P$为输入的数字,个数和次方数
  • 2、在遍历数字的时候就会发现,当数字$N$给定的时候,$N$的因子是有一个上界的(最大也就为$N$),那么我们可以首先获取$N$的因子的搜索上界,使用$sup$来保存。
  • 3、由于要求选择字典序最大的序列,那么我们优先从数字大的($sup$)开始搜索一直到1为止,这样在获取到$P$次方和为$N$,个数为$K$的序列的时候就只需要获取底数和最大的了
  • 4、DFS递归边界:如果找到$P$次方和为$N$,因子个数为$K$的序列,就获取底数更大的序列,如果是$P$次方和大于$N$或者因子个数大于$K$或者待选择因子等于0就回退。
  • 5、DFS递归体:首先,选择当前因子$factor$,将$factor$保存到$vector$<$int$>$ t$中,然后进入下一轮搜索,这里的搜索是可以继续选择当前因子。DFS(factor,sum+getPowerP(factor,P),num+1,N,K,P);如果不选择当前因子,那么先将t退出之前选择的因子,然后在进入下一轮搜索DFS(factor-1,sum,num,N,K,P);

这里使用了$vector$<$int$>$ result$存放最优解,每一次在获得一个符合条件的序列后,就使用$getLargerSequence$函数获得序列底数更大的序列,具体代码如下:

// 获得底数更大的序列
void getLargerSequence(){
    if(result.empty()){
        result = t;
    } else {
        int sumT = 0;
        int sumR = 0;
        for (int i = 0; i < t.size(); ++i) {
            sumT += t[i];
            sumR += result[i];
        }
        if(sumT>sumR){
            result.clear();
            result = t;
        }
    }
}
结果的输出:

如果result为空,说明没有解,输出Impossible,否则从前往后输出每一个因子(搜索是从大到小,所以就从前往后输出即可)

注意点:

  • 1、选择的因子的上界不能选择数字N,得通过计算获得其上界sup,不然测试点3,5,6,7运行超时
  • 2、在添加了上界sup条件后,如果是从1到sup进行搜索,会导致测试点5超时
  • 3、将搜索方向改为从大到小后,测试点2有可能会出现错误,第一个可能的原因是,没有添加选择底数之和更大的序列(注意不是字典序之和更大,与从1开始搜索的区别开)第二个原因可能是DFS的执行过程选择了先不选择因子factor,然后再选择因子factor,也会导致测试点2出错(测试结果是会导致序列乱序的情况,相当于一棵树,走到叶子节点后,再上去一点又下去,到叶子节点后又上去的情况,数字的选择呈现波浪)。
  • 4、如果以上还是不能解决运行超时的问题,可以考虑使用打表的技巧,将所有N的P次方下的因子全部求出存在在一个数组里面进行搜索(不一定有用)。
  • 5、vector的赋值操作使用引用赋值可以缩短时间(虽然不推荐使用)

提交结果:

image.png

AC代码:

#include <cstdio>
#include <vector>

using namespace std;

vector<int> t;//暂存当前搜索的一个解
vector<int> result;//存放最优解

// 获得底数更大的序列
void getLargerSequence(){
    if(result.empty()){
        result = t;
    } else {
        int sumT = 0;
        int sumR = 0;
        for (int i = 0; i < t.size(); ++i) {
            sumT += t[i];
            sumR += result[i];
        }
        if(sumT>sumR){
            result.clear();
            result = t;
        }
    }
}

// 获得factor的P次方
int getPowerP(int factor,int P){
    int r = 1;
    for (int i = 0; i < P; ++i) {
        r *= factor;
    }
    return r;
}

void DFS(int factor,int sum,int num,int N,int K,int P){
    if(sum==N&&num==K){
        // 找到平方和为N,因子个数为K的序列了
        getLargerSequence();// 保存更大的序列
        return;
    }
    if(sum>N||num>K||factor==0) return;
    //选择factor
    t.push_back(factor);
    DFS(factor,sum+getPowerP(factor,P),num+1,N,K,P);
    //不选择factor
    t.pop_back();
    DFS(factor-1,sum,num,N,K,P);
}

int main(){
    int N,K,P;// N为待比较的数字,K为因子个数,P为指数
    scanf("%d %d %d",&N,&K,&P);
    // 获得factor的上界
    int sup = 1;
    while (getPowerP(sup,P)<=N){
        ++sup;
    }
    DFS(sup-1,0,0,N,K,P);
    if(result.empty()){
        printf("Impossible");
    } else {
        printf("%d =",N);
        for(int i=0;i<result.size();++i){
            printf(" %d^%d",result[i],P);
            if(i<result.size()-1) printf(" +");
        }
    }
    return 0;
}
下面给出使用了打表技巧的代码,速度稍微快一点
#include<cstdio>
#include<vector>

using namespace std;
/*
题目要求:给定正整数N,K,P,将N表示为K个正整数的P次方和(递减排序),如果有多个选择底数和最小的方案,如果还有多个,选择底数序列字典序最大的序列
算法思路:题目的思路倒是不难,常规遍历都可以想得到,但是设计到的技巧还有有些,最容易想到的就是从1开始遍历数字,累加当前遍历数字的P次方和,找到相等于n的即可
那么在遍历数字的时候会出现选择该数或不选择该数的情况,很自然就想到深度优先遍历的思想.

1.在遍历数字的时候就会发现,当数字n给定的时候,n的因子是有一个上界的(最大也就为n),而且P最小还为2,那么为了方便起见,可以使用打表的技巧将可能为n的因子的数字
全部保存起来,然后问题就自然转化为在所有可能的因子中寻找组合是的因子的P次方和等于n,这样就好求的多,所以编写函数init(),起始数字从0开始(0不能取),将所有
数字的P次方小于等于N的添加进行数组factor中。其下标就是数字,对应的值就是该数字的P次方
2.在DFS中我们有4个参数count:统计因子的个数,sumF:因子的总和,sumQ:因子的p次方和,num:当前待添加的因子,同时也是factor的下标,遍历顺序我们可以从前往后,也可以
从后往前遍历,但是这里只能从后往前,后面再解释,DFS分为递归边界和递归体
    1)递归边界,第一个递归边界就是找到满足条件序列时,如果当前的序列的sumF更大,那么保存sumF并且更新序列,第二个递归条件就是找不到满足条件序列时,这里有3个
     num==0||count>k||sumQ>n,也即是0数字不能为因子,如果遍历到0说明遍历完毕,或者已经选择了k个数了,不能再选择,或者当前序列P次方和已经大于N了,不能再选择
    2)递归体:分为选择num数字和不选择num数字,当我们选择num数字的时候要注意一点,这里num可重复选择,所以递归时num不变,不选择时,num是减一
    
!!!!这里有一点让我百思不得其解的地方,就是如果不选择在选择的前面会在测试点2的地方出现错误 
说明:
1.如果是从前往后遍历得将递归失败num的条件该为num>factor.size()-1,判断上界. 
2.如果是从前往后遍历得那就得每次获得序列后判断是否比之前获得序列字典序更大,这样会导致测试点5超时,所以按照字典序大到小遍历数字会更快
3.判断没有因子的条件是maxFsum==-1
*/
vector<int> r,temp,factor;//保存最终的分解序列和每次遍历的分解序列 ,factor保存数字n可能的因子的p次方,不写这个可能会超时 
int n,k,p;//数字n,分解的个数,幂次
int maxFsum = -1;//保存最大因子的和 

//n的p次方 
int Pow(int n){
    int r = 1;
    for(int i=0;i<p;++i){
        r *= n;
    }
    return r;
}

void init(){//初始化factor数组 
    for(int i=0;Pow(i)<=n;++i){
        factor.push_back(Pow(i));
    }
}
/*
count:统计因子的个数
sumF:因子的总和
sumQ:因子的p次方和 
num:当前待添加的因子,同时也是factor的下标 
*/
void DFS(int count,int sumF,int sumQ,int num){
    if(count==k&&sumQ==n){//递归边界,找到了p次方和为n并且因子个数为k的序列了
        if(maxFsum<sumF){//temp的序列因子和更大 
            r = temp;//将temp赋值给r
            maxFsum = sumF;
        }
        return;
    }
    //num>factor.size()-1,如果是从1开始往前遍历的话,得改变num的判断条件为这句话,因为上界就是factor.size()-1,不然会进入无限递归 
    if(num==0||count>k||sumQ>n) return;//不能为数字0,已经选择了k个数字,或者p次方和大于n,直接返回
//    DFS(count,sumF,sumQ,num-1);//不选择num,如果先不选择num的话,测试点2过不了 
    temp.push_back(num);
    DFS(count+1,sumF+num,sumQ+factor[num],num);//num的选择可能有多个,这里额外要注意 
    temp.pop_back();//退出当前num
    DFS(count,sumF,sumQ,num-1);//不选择num
}

int main(){
    scanf("%d %d %d",&n,&k,&p);
    init();
    DFS(0,0,0,factor.size()-1);//从最后一个可能的因子开始选择
    if(maxFsum==-1){//没有因子 
        printf("Impossible");
    } else{//输出r即可,不过得倒序输出 
        printf("%d = ",n);
        int len = r.size();
        for(int i=0;i<len;++i){
            if(i==0){
                printf("%d^%d",r[i],p);
            }else{
                printf(" + %d^%d",r[i],p);
            }
        }
    }
    return 0;
} 

乔梓鑫
569 声望17 粉丝

主要分享个人学习经验和心得