PAT甲级1103 Integer Factorization (30分) ***** 有难度 测试点2分析, 递归写的再规范点

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

题目倒是好读懂,关键是如何构建这个等式,我没想到太好的办法,就是准备用深搜试试,题目给出的N最大也就400,最初感觉良好,结果还是为了时间优化了好久,另外这题可以好好整理下递归深搜的标准写法。

先说测试点2吧,这个测试点的情况应该是 因子和相同的情况有两种,打个比方一个情况是 6 6 后面跟着几个数字, 6 5 后面跟着几个数字,两个都是满足的,你需要输出第一种情况,大家可以参考下。

用深搜的话,最直观的思路是对每个子式的所有可能的数字都遍历一遍(所有可能的是指从1 到maxLimit=pow(N,(double)1/P); N开P次方) ,样例中的169 开2次方是13,169应该由5个[1,13]之间数的二次方相加构成的。

但是按照上面说的,如果按照K个位置,每个位置从1到maxLimit 深搜的话我写的代码时间会爆炸,有测试点过不了,而且对于样例的那种情况会出现6 6 6 6 5,5 6 6 6 6 这种全排列的情况,这肯定是需要优化的,结果当时没想到如何优化,后来看了看柳神的代码,发现了优化思路。。。。。。。。小尴尬
柳神本题的博客

详细看了下柳神代码,为了避免遇到全排列的情况,也是对每个位置的数字进行递归,但是递归时候多一个因子上限参数index,这个参数限制你在该位置数字的最大上限,巧妙地避免了遇到全排列的情况

比如样例的那个169 5 2,在第一个位置的递归,index可以指向13 12 11 10 一直到1的平方,但是在第二个位置递归函数时候index的上限就是第一次index所指向的值,换句话说就是第二次递归的时候传入的Index是12 那么第二个位置只能考虑1 到12,第二次递归的时候传入的index是9,那么第二个位置只能考虑1到9的数字。 这样感觉很巧妙实现了递归的数字都是降序排列的,不会遇到我最初想的那种排列的情况。

这里总结下看完柳神题解的感觉吧

  • 我是习惯使用一个vector把所有的K个位置的数字放到vector里面,然后深搜,深搜完毕后再pop,如下

    int pos=0;
    
    while(pow(factor[pos],P)<=num&&pos<factor.size())
    {
    
    
        solution.push_back(factor[pos]);
    
        df(num-pow(factor[pos],P),factorNum-1);
    
        solution.pop_back();
        pos++;
    }
    
    

    其实完全没必要pop_back了,只要再加一个保存当前递归位置的递归变量就行,temkp记录当前是放置第几个位置就好了,到时候只需要考虑在temkp的位置放置那个数字,而不用考虑去除,后面递归时候会自己覆盖

  • 然后就是构造一个Pow数组,不用每次计算Pow了

  • 还有就是把factorSum也当成一个递归的变量,而且柳神的递归方式确保了子式数字选择的时候从大到小,这样在最终结果判断的时候只考虑factorSum比当前的大了,保存本次的结果,factorSum相等的时候,因为因子从大到小判断,所以第一次找到的最大和是字典序最大了的,不用改变,这思路,啧啧啧

     if (tempK == k) {
    
            if (tempSum == n && facSum > maxFacSum) {
                    ans = tempAns;
                    maxFacSum = facSum;
            }
           
            return;
        }
    

我后面AC的代码走了另一条路子
我们不从位置上看了,从每个可能数字的数量上考虑,题目中样例是169 5 2,在结果的5个数中,每个数可能是1到13,那么最终结果含有0个1 ,含有1个,两个1,三个1,四个1,5个1(这个可以直接判断和不为169 return掉),这五种情况,每个还可以继续对含有几个2递归 ,可能有0个2 ,1个2 ,3个2,4个2 ,然后对3的个数进行递归,一直到13。

这样时间还是不太能过,再优化,为了更节省时间,从13 到1进行判断,从含有几个13,含有几个11,到了看含有几个1这一步时候,我们完全可以看剩余的和 以及剩余的子式数目来判断了,更简单,如果反过来先看含有几个1就很蛮了,如果K是100,那么在第一层含有几个1的递归中,就需要进行100次,太多了。

然后为了直接可以输出最终结果,不对和相同的结果进行排序,我们在对含有几个13递归的时候,先考虑含有最多的13,而不是先考虑含有1个13的情况,这样可以确保我们首先得到的结果肯定是满足字母序的,比如测试点2两种情况,6 6 **** 和6 5 *** ,我们需要确保首先递归得到的是 6 6 开头的结果,所以必须要先考虑最多12,也就是5个12的情况,然后考虑4个12的情况。

AC代码
时间如如下
在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;
int N,K,P;
vector<int> factor;
vector<int> out;
vector<int> solution;

int maxSum=-1;
int cmp(vector<int>a ,vector<int> b)
{
    for(int i=0; i<a.size(); i++)
    {
        if(a[i]!=b[i])
            return a[i]>b[i];
    }
}


void df(int sum,int factorNum,int pos)
{
    if(factorNum==0&&sum==0)
    {
        int cacheSum=0;
        for(int i=0; i<solution.size(); i++)
        {
            cacheSum+=solution[i];
        }
        if(cacheSum>maxSum)
        {
            maxSum=cacheSum;
            out=solution;

        }
        return ;
    }
    else if(factorNum==0||sum<factorNum||sum<=0||pos>=factor.size())
        return ;

    //样例之中就是 计算12^2次方
    int getnum=pow(factor[pos],P);

    //169最多含有几个12^2 ,这个数字其实还要和当前可以有的子式数目比较下factorNum
    //也是为了加快速度,比如169 最多有42个2^2  而factorNum最多也就5个,我们应该说
    //这里最多也就5个2了
    int maxNum=sum/getnum;
	

    if(maxNum>factorNum)
        maxNum=factorNum;
     	//这里就是先压入最多可能的因子,后面再一个个弹出
    for(int i=1; i<=maxNum; i++)
        solution.push_back(factor[pos]);
	//这里就是先考虑 5个13 ,弹出一个13,考虑4个13,弹出一个13,考虑3个13,直到没有13这个数字
    for(int i=maxNum; i>=0; i--)
    {

        df(sum-getnum*i,factorNum-i,pos-1);
         if(i>0)
            solution.pop_back();
    }





}
int main()
{

    cin>>N>>K>>P;
    int maxLimit=pow(N,(double)1/P);
    for(int i=1; i<=maxLimit; i++)
    {
        factor.push_back(i);
    }
    //factor.size()-1 是说先从可选择的最大的子式进行,不如169 最大是12^2
    //12在factor里面的位置就是 factor.size()-1
    df(N,K,factor.size()-1);
    if(out.size()>0)
    {
        //sort(out.begin(),out.end(),cmp);
        printf("%d = %d^%d",N,out[0],P);
        for(int q=1; q<out.size(); q++)
        {
            printf(" + %d^%d",out[q],P);
        }
    }
    else
        cout<<"Impossible";

    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值