题目倒是好读懂,关键是如何构建这个等式,我没想到太好的办法,就是准备用深搜试试,题目给出的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;
}