【题解】CF758D Ability To Convert

这道题本来是可以贪心的,贪心思路楼下也讲过了,不过因为部分细节原因,这道题的贪心我打挂了(惨不忍睹连续 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值