这道题本来是可以贪心的,贪心思路楼下也讲过了,不过因为部分细节原因,这道题的贪心我打挂了(惨不忍睹连续 WA 10 次),所以这道题我还是可怜地打 dp 去了(神犇勿喷)…
言归正传,这道题的区间 dp 思路是这样的:我们先用一个 f[i][j] 表示从第 i 位到第 j 位组成的十进制数是多少。比如对于一个数 1235 来说,f[2][4]=235; 在此基础上,dp[i][j] 表示从第 i 位到第 j 位,按照当前 n 进制转换,得出的最小结果是多少。
那么很显然,每一位 k 上,如果把当前 k+1~r 这段区间转成一个 n 进制数的一位,那么前面的 l~k 段(也是 n 进制数的一位)的指数一定比 k+1~r 多一。所以 dp[l][r]=min(dp[l][r],dp[l][k]·10+dp[k+1][l])
不过,对于每一个 dp[i][j] 所用到的区间需要判断当前状态所用到的 n 进制数是否合法,所以使用记忆化搜索就可以了。
具体细节还有一个,如果数字里面的某一段连续的 0 数量超过了 n 的位数-1,那么前(n的位数-1)个 0 可以并到前面的一个 n 进制数后面,剩下的每一个都是一位 0(因为每一个 n 进制数都不含前导 0),也就是说后面有几个0,前面就多乘几个 n 。这个细节很重要,一定要处理好。
最后说一点,绝对不要使用 cmath 里面的 pow 函数,自己写快速幂都比那个好(我就因为这个问题 WA 了五次╮(╯﹏╰)╭)。。。
好了下面就是代码了:
#include<iostream>
#define INF 9223372036854775807ll //long long max
using namespace std;
unsigned long long n,m,ans,f[105][105],dp[105][105];
string a;
int dig(unsigned long long a) //a 在十进制下有几位
{
int ans=0;
while(a) a/=10,ans++;
return ans;
}
unsigned long long quick_pow(unsigned long long a,int mi)
{
unsigned long long b=1;
while(mi) {if(mi&1) b*=a; a*=a,mi>>=1;}
return b;
}
unsigned long long dfs(int i,int j) //记忆化搜索
{
if(dp[i][j]!=INF) return dp[i][j];
int state=0,p; //state 表示后面有几个0,p 表示从第 p+1 位开始有连续的一串 0
for(p=j;a[p]=='0' && p>=i;p--)
state++;
state-=dig(n)-1; //前(n的位数-1)个 0 可以并到前面的一个 n 进制数后面
if(state>0) return dp[i][j]=dp[i][j],quick_pow(n,state)*dfs(i,p+dig(n)-1); //特判多乘 state 个 n
if(f[i][j]<n) return dp[i][j]=f[i][j];
for(int k=i;k<=j-1;k++)
{
if(a[k+1]!='0' || (a[k+1]=='0' && k+1==j))
dp[i][j]=min(dp[i][j],dfs(1,k)*n+dfs(k+1,j)); //转移
}
return dp[i][j];
}
int main()
{
cin>>n>>a,m=a.size(),a=" "+a;
for(int i=1;i<=m;i++)
for(int j=1;j<=m;j++)
f[i][j]=dp[i][j]=INF;
for(int i=1;i<=m;i++) dp[i][i]=f[i][i]=a[i]-'0';
for(int len=2;len<=dig(n);len++) //枚举区间长度
for(int i=1;i+len-1<=m;i++)
{
int j=i+len-1;
f[i][j]=f[i][j-1]*10+a[j]-'0'; //初始化 f 数组
}
cout<<dfs(1,m)<<endl;
return 0;
}
本文介绍了一种使用区间动态规划(DP)解决数位DP问题的方法,通过实例讲解了如何利用记忆化搜索优化计算过程,避免重复计算,特别强调了处理连续零的细节,以及避免使用cmath库中pow函数的重要性。
6057

被折叠的 条评论
为什么被折叠?



