数位dp

1

[SCOI2014]方伯伯的商场之旅
题意: n n 个人,每个人面前有几堆石子。第 i 个人面前的第 j j 堆的石子的数量是 i 写成 K K 进制后的第 j 位。每次操作可以选择一个人面前的两堆石子,将其中的一堆中的某些石子移动到另一堆,代价是移动的石子数量 * 移动的距离。要把位置在 [L,R] [ L , R ] 中的每个人的石子都合并成一堆石子,求最小代价。

数位 dp d p 的套路。考虑已经填了最高几位数,满足这些数要求的全部的数的代价。
我们先假设把所有的石子都移动到最低位上的代价,然后依次枚举从 i>i+1i+1 i − > i + 1 i + 1 更优的数字有多少个。(好神啊)。

// luogu-judger-enable-o2
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
ll f[61][3010];
int a[20],k;
ll dfs1(int pos,int limit,ll sum)
{
    if(!pos) return sum;
    if((~f[pos][sum])&&(!limit)) return f[pos][sum];
    int num= limit?a[pos]:k-1;
    ll ans=0;
    for(int i=0;i<=num;i++)
    {
        ans+=dfs1(pos-1,limit&&i==a[pos],sum+i*(pos-1));
    }
    if(!limit) f[pos][sum]=ans;
    return ans;
}
ll dfs2(int pos,int limit,int d,ll sum)
{
    if(sum>0) return 0;
    if(!pos) return sum;
    if((!limit)&&(~f[pos][-sum])) return f[pos][-sum];
    int num= limit?a[pos]:k-1;
    ll ans=0;
    for(int i=0;i<=num;i++)
    {
        if(pos>=d) ans+=dfs2(pos-1,limit&&i==a[pos],d,sum-i);
        else ans+=dfs2(pos-1,limit&&i==a[pos],d,sum+i);
    }
    if(!limit) f[pos][-sum]=ans;
    return ans;
}
ll solve(ll x)
{
    int cnt=0;
    while(x)
    {
        a[++cnt]=x%k;
        x/=k;
    }
    memset(f,-1,sizeof(f));
    ll ans=dfs1(cnt,1,0);
    for(int i=2;i<=cnt;i++)
    {
        memset(f,-1,sizeof(f));
        ans+=dfs2(cnt,1,i,0);
    }
    return ans;
}
int main()
{
    ll l,r;
    cin>>l>>r>>k;
    cout<<solve(r)-solve(l-1); 
    return 0;
}

2

[CQOI2016]手机号码
题意以下的号码被称作幸运号码:号码中要出现至少 3 个相邻的相同数字,且号码中不能同时出现 8 和 4 。手机号码一定是 11 位数,不含前导的 0 。输出 [L,R] 区间内所有满足条件的号码数量。

dp的时候直接记录位置,上一个数是什么,当前连续出现了几个相同的数,先前有没有出现过连续的三个数,卡不卡上界。然后暴力转移。
注意如果l=1e10,那么l-1是十位数,注意前导0的处理。(不然会有连续的三个前导0被计算进答案)。方法是强制最高位不能是0,或者记录前面的位有没有填过数。

// luogu-judger-enable-o2
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
ll f[20][15][5][2][2];
int a[20],cnt;
ll dfs(int pos,int limit,int pre,int last,int sta4,int sta8)
{
    if(sta4&&sta8) return 0;
    if(!pos) return last>=3;
    if((!limit)&&(~f[pos][pre][last][sta4][sta8])) return f[pos][pre][last][sta4][sta8];
    int num=limit?a[pos]:9;
    ll ans=0;
    for(int i=pos==cnt;i<=num;i++)
    {
        if(last==0) ans+=dfs(pos-1,limit&&i==a[pos],i,1,sta4||i==4,sta8||i==8);
        else if(last==3) ans+=dfs(pos-1,limit&&i==a[pos],i,3,sta4||i==4,sta8||i==8);
        else ans+=dfs(pos-1,limit&&i==a[pos],i,i==pre?last+1:1,sta4||i==4,sta8||i==8);
    } 
    if(!limit) f[pos][pre][last][sta4][sta8]=ans;
    return ans;
}
ll solve(ll x)
{
    cnt=0;
    while(x)
    {
        a[++cnt]=x%10;
        x/=10;
    }
    return dfs(cnt,1,0,0,0,0);
}
int main()
{
    ll l,r;
    cin>>l>>r;
    memset(f,-1,sizeof(f));
    if(l==1e10)
    {
        cout<<solve(r);
        return 0;
    }
    cout<<solve(r)-solve(l-1);
    return 0;
}

3

[AHOI2009]同类分布
题意:给出两个数 a,b,求出[a,b]中各位数字之和能整除原数的数的个数。
我们枚举各位数字之和a,问题转化成有多少数的各位数字之和是a且能被a整除。我们发现只需要关注对a取模的余数。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
ll f[20][180][180],mod,a[20];
ll dfs(int pos,int limit,int r,int sum)
{
    if(pos*9+sum<mod||sum>mod) return 0;
    if(!pos) return r==0&&sum==mod;
    if((!limit)&&(~f[pos][r][sum])) return f[pos][r][sum];
    int num=limit?a[pos]:9;
    ll ans=0;
    for(int i=0;i<=num;i++)
    {
        ans+=dfs(pos-1,limit&&i==a[pos],(r*10+i)%mod,sum+i);
    }
    if(!limit) f[pos][r][sum]=ans;
    return ans;
}
ll solve(ll x)
{
    int cnt=0;
    while(x)
    {
        a[++cnt]=x%10;
        x/=10;
    }
    ll ans=0;
    for(int i=1;i<=cnt*9;i++)
    {
        mod=i;
        memset(f,-1,sizeof(f));
        ans+=dfs(cnt,1,0,0);
    }
    return ans;
}
int main()
{
    ll l,r;
    cin>>l>>r;
    cout<<solve(r)-solve(l-1);
    return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值