数位DP

定义数位dp数组为 f [ x ] [ s t 1 ] [ s t 2 ] . . . [ s t n ] f[x][st1][st2]...[stn] f[x][st1][st2]...[stn]表示第x位状态为 s t 1... s t n st1...stn st1...stn的值。不要把 l i m lim lim放到数组里面,因为当lim = 1时,对于每个x只会进入一次。有时候这样能避免dp数组的每次初始化。

Windy 数

Windy定义了一种 Windy 数:不含前导零且相邻两个数字之差至少为2的正整数被称为Windy数。Windy想知道,在A和B之间,包括A和B,总共有多少个 Windy 数?

#include<bits/stdc++.h>
using namespace std;
int f[12][12],digit[12],cnt;
int dfs(int x,int st,int op){  // x为当前为第几位 st为前缀状态 op表示前缀和当前求解的数字的关系,1为相同 0为小于
    if(!x)return 1;
    if(!op && ~f[x][st])return f[x][st]; // 记忆化
    int maxx=op ? digit[x] : 9;
    int res=0;
    for(int i=0;i<=maxx;++i){
        if(abs(i-st)<2)continue;
        if(st==11 && i==0)res+=dfs(x-1,11,op && i==maxx);
        else res+=dfs(x-1,i,op && i==maxx);
    }
    if(!op)f[x][st]=res;
    return res;
}
int solve(int n){
    cnt=1;
    while(n){
        digit[cnt++]=n%10;
        n/=10;
    }
    return dfs(cnt-1,11,1)-1;  // -1排除0
}
int main(){
    memset(f,-1,sizeof f);
    int a, b;
    while(cin>>a>>b)
        cout<<solve(b)-solve(a-1)<<endl;
}

数字计数

求a和b之间的整数,每个数码出现的数量。

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
class DP{
public:
    ll nums;
    vector<ll> v;
    DP(ll n=-1):nums(n),v(10) {}
    DP operator+ (const DP &x) const{
        DP res(nums+x.nums);
        for(int i=0;i<10;++i)
            res.v[i] = v[i] + x.v[i];
        return res;
    }
    DP operator- (const DP &x) const{
        DP res(nums-x.nums);
        for(int i=0;i<10;++i)
            res.v[i] = v[i] - x.v[i];
        return res;
    }
};
DP f[15];
int dig[15],cnt;
DP dfs(int x,int st,int op){   // st表示有没有前导零 op表示前缀和要求的数的是否相同
    if(!x)return DP(1);
    if(!st && !op && ~f[x].nums)return f[x];
    int maxx = op ? dig[x] : 9;
    DP res(0);
    for(int i=0;i<=maxx;++i){
        if(st && i==0){  // 排除前导零
            res = res + dfs(x-1, 1, op && i==maxx);
        }else{
            DP tmp = dfs(x-1, 0, op && i==maxx);
            res = res + tmp;
            res.v[i] += tmp.nums;
        }
    }
    if(!st && !op)f[x] = res;
    return res;
}
DP solve(ll x){
    cnt = 1;
    for(int i=0;i<15;++i)
        f[i] = DP(-1);
    while(x){
        dig[cnt++] = x%10;
        x /= 10;
    }
    return dfs(cnt-1, 1, 1);
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    ll a,b;
    while(cin>>a>>b){
        DP b_ans = solve(b), a_ans = solve(a-1);
        DP res = b_ans - a_ans;
        for(int i=0;i<10;++i){
            cout<<res.v[i]<<' ';
        }
        cout<<endl;
    }
}

同类分布

给出两个数a,b,求出[a,b]中各位数字之和能整除原数的数的个数。
f[x][st][val],x为当前所在位,st为前缀和数字和,val为前缀值模mod的值。

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll = long long;
using pii = pair<int,int>;
const int maxn=3e5+3;
int mod, dig[20];
ll f[20][170][170];
ll dfs(int x,int st,int val,int lim){
    if(!x)return st == mod && val == 0;  // 当前缀为mod,值%mod为0时有贡献
    if(st>mod)return 0;
    if(!lim && ~f[x][st][val])return f[x][st][val];
    int maxx = lim ? dig[x] : 9;
    ll res=0;
    for(int i=0;i<=maxx;++i){
        res += dfs(x-1,st+i,(val*10+i)%mod,lim && i==maxx);
    }
    if(!lim)f[x][st][val] = res;
    return res;
}
ll solve(ll x){
    ll ans=0;
    int cnt=0;
    while(x){
        dig[++cnt] = x % 10;
        x /= 10;
    }
    for(int i=1;i<162;++i){
        memset(f,-1,sizeof f);
        mod = i;
        ans += dfs(cnt, 0, 0, 1);
    }
    return ans;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    ll a,b;
    cin>>a>>b;
    cout<<solve(b)-solve(a-1)<<endl;

}

Valley Numer

当一个数字,从左到右依次看过去数字没有出现先递增接着递减的“山峰”现象,就被称作 Valley Number。它可以递增,也可以递减,还可以先递减再递增。在递增或递减的过程中可以出现相等的情况。比如,1,10,12,212,32122都是 Valley Number。
求不大于N的Valley Number数有多少。
f[x][st][up][ish],x为当前位,st为上一个数字的值,up为现在是否为递增,ish为现在是否为最高位。

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll = long long;
using pii = pair<int,int>;
const int maxn=1e2+3;
const ll mod=1e9+7;
char dig[maxn];
int f[maxn][10][2][2],cnt=0;
ll dfs(int x,int st,bool up,bool ish,int lim){
    if(x==cnt){  return 1;}
    if(!lim && ~f[x][st][up][ish])return f[x][st][up][ish];
    int maxx = lim ? dig[x]-'0' : 9;
    ll res = 0;
    for(int i=0;i<=maxx;++i){
        if(ish && i==0){
            res = (res+dfs(x+1,9,0,1,lim && i==maxx))%mod;
        }else if(up){
            if(i<st)continue;
            res = (res+dfs(x+1,i,1,0,lim && i==maxx))%mod;
        }else{
            if(i<=st) res = (res+dfs(x+1,i,0,0,lim && i==maxx))%mod;
            else res = (res+dfs(x+1,i,1,0,lim && i==maxx))%mod;
        }
    }
    if(!lim)f[x][st][up][ish] = res;
    return res;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int T;
    cin>>T;
    while(T--){
        cin>>dig;
        cnt = strlen(dig);
        memset(f,-1,sizeof f);
        cout<<dfs(0,9,0,1,1)-1<<endl;
    }
}

当数位dp数组中不放lim时,可以只memset一次。

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll = long long;
using pii = pair<int,int>;
const int maxn=1e2+3;
const ll mod=1e9+7;
char dig[maxn];
int f[maxn][10][2][2],cnt=0;
ll dfs(int x,int st,bool up,bool ish,int lim){
    if(x==-1)return 1;
    if(!lim && ~f[x][st][up][ish])return f[x][st][up][ish];
    int maxx = lim ? dig[x]-'0' : 9;
    ll res = 0;
    for(int i=0;i<=maxx;++i){
        if(ish && i==0){
            res = (res+dfs(x-1,9,0,1,lim && i==maxx))%mod;
        }else if(up){
            if(i<st)continue;
            res = (res+dfs(x-1,i,1,0,lim && i==maxx))%mod;
        }else{
            if(i<=st) res = (res+dfs(x-1,i,0,0,lim && i==maxx))%mod;
            else res = (res+dfs(x-1,i,1,0,lim && i==maxx))%mod;
        }
    }
    if(!lim)f[x][st][up][ish] = res;
    return res;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int T;
    cin>>T;
    memset(f,-1,sizeof f);
    while(T--){
        cin>>dig;
        cnt = strlen(dig);
        reverse(dig,dig+cnt);  // 保持f数组下标0为个位
        cout<<dfs(cnt-1,9,0,1,1)-1<<endl;
    }
}

Beautiful numbers

9 e 18 9e18 9e18范围内,[a,b]之间的数中符合<每个非零数位都能整除这个数>的数的数量。思路:问题可转化为每个非零数位的lcm能整除这个数。由于2520是1-9的lcm,问题转化为每个非零数位的lcm能整除这个数取模2520.
f[x][st][op],x为当前位,st为前缀lcm,op为当前的前缀取模2520.
特殊:由于f数组和lim无关,所以在不同次查询中都能用!只要初始化一次! 否则会超时。

#include<bits/stdc++.h>
using namespace std;
//#define endl '\n'
using ll = long long;
using pii = pair<int,int>;
const int maxn=1e2+3;
const ll mod = 2520;
int arr[][4]={{1,2,4,8},{1,3,9},{1,5},{1,7}}, na[]={4,3,2,2};
ll f[20][50][2523];
int dig[20],ma[2523];
vector<int> num;
int gcd(int a,int b){
    return a%b ? gcd(b,a%b) : b;
}
void init(int x,int now){
    if(x&4){num.push_back(now);return;}
    for(int i=0;i<na[x];++i)
        init(x+1,arr[x][i]*now);
}
ll dfs(int x,int st,int op,int lim){
    if(!x)return op%st==0;
    if(!lim && ~f[x][ma[st]][op])return f[x][ma[st]][op];
    int maxx = lim ? dig[x] : 9;
    ll ans=0;
    for(int i=0;i<=maxx;++i){
        if(i==0 || st % i == 0)
            ans += dfs(x-1,st,(op*10+i)%mod,lim&&i==maxx);
        else
            ans += dfs(x-1,st*i/gcd(st,i),(op*10+i)%mod,lim&&i==maxx);
    }
    if(!lim)f[x][ma[st]][op]=ans;
    return ans;
}
ll solve(ll x){
    int cnt=0;
    while(x){
        dig[++cnt] = x % 10;
        x /= 10;
    }
    return dfs(cnt, 1, 0, 1);
}
int main(){
    memset(f,-1,sizeof f);
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    init(0,1);
    for(int i=0;i<num.size();++i)
        ma[num[i]] = i;
    int T;
    cin>>T;
    while(T--){
        ll a,b;
        cin>>a>>b;
        cout<<solve(b)-solve(a-1)<<endl;
    }
}

P3286 [SCOI2014]方伯伯的商场之旅

题意:对于一个数 i i i,把它看成 k k k进制数,把所有位上的数移动到一个位上,代价是移动的石子数量 × 移动的距离。问把 [ L , R ] [L,R] [L,R]内每个数都进行一次合并,求最小合并代价。
思路:先假设所有数都合并到个位,很容易能得到总代价。然后考虑移动。当一个n位数的 s u m [ n . . . i ] > s u m [ i − 1...1 ] sum[n...i]>sum[i-1...1] sum[n...i]>sum[i1...1] 时,它合并到的点必然 ≥ i ≥i i,所以并且代价应该减少 s u m [ n . . . i ] − s u m [ i − 1...1 ] sum[n...i]-sum[i-1...1] sum[n...i]sum[i1...1]。所以可以贪心一位一位进行数位DP。

#include<bits/stdc++.h>
using namespace std;
//#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0), cout.tie(0)
typedef long long LL;
const int N = 5e4 + 5, K = 105;
const int inf = 1e9;
const long long mod = 1e9 + 7;
using LD = long double;
using LL = long long;
using PII = pair<int, int>;
/*
* long long !!!
* array size !!!
*/
int dig[1005];
LL f[105][1505], pos, k;
LL dfs(int x, int st, int lim){
    if(pos > 1 && st < 0)return 0;
    if(!x)return st;
    if(!lim && ~f[x][st])return f[x][st];
    int maxx = lim ? dig[x] : k - 1;
    LL res = 0;
    for(int i = 0; i <= maxx; ++i){
        if(pos == 1)res += dfs(x-1, st + i * (x - 1), lim && i == dig[x]);
        else if(x >= pos)res += dfs(x-1, st + i, lim && i == dig[x]);
        else res += dfs(x-1, st - i, lim && i == dig[x]);
    }
    if(!lim)f[x][st] = res;
    return res;
}
LL solve(LL n){
    int cnt = 0;
    while(n){
        dig[++cnt] = n % k;
        n /= k;
    }
    pos = 1;
    memset(f, -1, sizeof(f));
    LL ans = dfs(cnt, 0, 1);
    for(pos = 2; pos <= cnt; ++pos){
        memset(f, -1, sizeof(f));
        //cout << ans << endl;
        ans -= dfs(cnt, 0, 1);
    }
    return ans;
}

int main(){
    IOS;
    LL l, r;
    cin >> l >> r >> k;
    cout << solve(r) - solve(l - 1) << endl;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值