題目倒是好讀懂,關鍵是如何構建這個等式,我沒想到太好的辦法,就是準備用深搜試試,題目給出的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;
}