P1066 月的第十二章 官方题解
【部分分:测试点 1∼41\sim 41∼4】
- 当 时,我们可以把已经写完的行数 看作状态。从 出发,枚举 ,转移到状态 ,代价为 。
- 从 开始做最短路即可得到答案。所有转移始终令 增大,所以也可以按照 递增的顺序进行动态规划。对每个不同的 记忆化答案后,不需要为重复数据重新计算。单次计算的时间复杂度为 ,空间复杂度为 。
【部分分:测试点 5∼85\sim 85∼8】
- 特殊性质保证 为质数。对于任意 ,都有 ,所以每个夜晚只能增加一行。
- 从 增加到 恰好需要 个夜晚,单组数据可以在 的时间内回答。这个性质提示我们:真正影响一次续写幅度的是 的约数,而不需要保留所有中间位置。
【性质观察】
- 设当前已经到达 ,并令 。若接下来始终增加 ,那么到达的每个中间位置仍然是 的倍数,它与 的最大公约数至少为 ,所以这些操作始终合法。
- 因此,从约数 出发到达更大的约数 ,我们至多需要
个夜晚。若途中得到更大的最大公约数,就把它作为新的约数状态,继续进行同样的讨论。
【部分分:测试点 9∼129\sim 129∼12】
- 第一档的瓶颈是保留了 的全部位置。根据上一页的观察,我们只保留 的正约数。将它们从小到大写成 ,其中 ,。定义 表示从 到达 所需的最少夜晚数,初值为 。
- 枚举上一个约数状态,完整转移为
答案为 ,不存在的状态视为正无穷。
【正解】
- 考虑一条最优续写过程。如果某一段过程在最大公约数为 时没有到达新的约数状态,就可以把这一段调整为不断增加 ,直至抵达下一次出现的约数状态。这样不会增加夜晚数。于是总存在一条最优过程只需记录依次经过的约数。
- 枚举两个相邻的约数状态时,上页转移恰好计算了它们之间所需的最少夜晚数,因此动态规划既不会遗漏更优过程,也不会加入非法过程。
- 试除分解 并递归生成全部约数。设 为约数个数,单组数据的时间复杂度为 ,空间复杂度为 。相同的 可以直接复用答案。
【参考代码】
/*Author:EhundateghDate:2026/8/18Name:moon_no_cht.cppYou steal,I kill.*/#include <map>#include <cstdio>#include <vector>#include <climits>#include <algorithm>#define MAXV 31630using namespace std; int c,T,y,Prime[4000],cnt=0;bool Tag[MAXV];map <int,int> Ans; void Init(){ for(int i=2;i<MAXV;i++){ if(!Tag[i]) Prime[++cnt]=i; for(int j=1;j<=cnt&&i*Prime[j]<MAXV;j++){ Tag[i*Prime[j]]=true; if(i%Prime[j]==0) break; } }} void Get_Div(int Now,long long Val,vector <pair<int,int> > &Fac,vector <int> &Div){ if(Now==(int)Fac.size()){ Div.push_back((int)Val); return; } for(int i=0;i<=Fac[Now].second;i++){ Get_Div(Now+1,Val,Fac,Div); if(i<Fac[Now].second) Val*=Fac[Now].first; }} int Calc(int y){ if(Ans.count(y)) return Ans[y]; int Temp=y; vector <pair<int,int> > Fac; for(int i=1;i<=cnt&&1ll*Prime[i]*Prime[i]<=Temp;i++){ if(Temp%Prime[i]) continue; int Count=0; while(Temp%Prime[i]==0){Temp/=Prime[i];Count++;} Fac.push_back(make_pair(Prime[i],Count)); } if(Temp>1) Fac.push_back(make_pair(Temp,1)); vector <int> Div; Get_Div(0,1,Fac,Div); sort(Div.begin(),Div.end()); vector <int> Dp(Div.size(),0); for(int i=1;i<(int)Div.size();i++){ int Best=INT_MAX; for(int j=i-1;j>=0;j--){ int Step=(Div[i]-1)/Div[j]; if(Step>=Best) break; Best=min(Best,Dp[j]+Step); } Dp[i]=Best; } return Ans[y]=Dp.back();} int main(){ Init(); scanf("%d%d",&c,&T); while(T-->0){ scanf("%d",&y); printf("%d\n",Calc(y)); } return 0;}