数位DP专题

洛谷 - 2602

dp数组的类型不一定是int,要看维护的值是什么,这道题维护的是0~9数字出现的次数。

dfs1其实只有两种情况,且都可以直接返回。

  • (limit=1)时,返回pos位后的数字;
  • (limit=0)时,返回(10^{pos})
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
typedef long long ll;
int a[20];
vector<ll> dp[20][2], state(10);
ll f[20];
ll dfs1(int pos, int limit) {
    if(pos < 0) return 1;
    if(!limit && f[pos] >= 0) return f[pos];
    
    int up = limit ? a[pos]: 9;
    ll res = 0;
    for (int i = 0 ; i <= up; i++) {
        res += dfs1(pos-1, limit&&i==up);
    }
    if(limit) return res;
    return f[pos] = res;
}

vector<ll> dfs(int pos, int limit, int lead) {
    if(pos < 0) return state;
    if(!limit && !dp[pos][lead].empty())
        return dp[pos][lead];
    
    vector<ll> res(10);
    
    int up = limit ? a[pos]: 9;
    
    for (int i = 0; i <= up; i++) {
        vector<ll> arr = dfs(pos-1, limit&&i==up, lead&&!i);
        
        if(!lead || i) res[i] += dfs1(pos-1, limit&&i==up);
        for (int d = 0; d <= 9; d++) {
            res[d] += arr[d];
        }
    }
    
    if(limit) return res;
    return dp[pos][lead] = res;
}

vector<ll> solve(ll x) {
    for (int i = 0; i < 20; i++) {
        dp[i][0].clear();
        dp[i][1].clear();
        f[i] = -1;
    }
        
    int pos = 0;
    while (x) {
        a[pos++] = x % 10;
        x /= 10;
    }
    return dfs(pos-1, 1, 1);
}

int main() {
    ll a, b;
    scanf("%lld %lld", &a, &b);
    auto arr1 = solve(b), arr2 = solve(a-1);
    for (int i = 0; i <= 9; i++) {
        ll num = arr1[i] - arr2[i];
        printf("%lld ", num);
    }
}

CF - 55D

区间内有多少能被它每一个非零的数位整除的正整数?

初步的想法是暴力维护带权和对每一位(0~9)的取模,但发现这样不知道有哪些数字出现过,哪些没出现,也就是还要维护出现的数字,这个状态设计无疑是失败的。

那怎么设计出好的(易于转移的)状态?

考虑用数学方法优化,“被出现的每一位数字整除”等价于“被每一位数字的的最小公倍数整除”。

于是乎带权和对lcm(0..9)=2520取模就是了,而”每一位数字的最小公倍数“会出现最多48种(2520的因子数),离散化一下可以减少空间的开销。

注意这里带权和的转移方法,不知道为什么用数组预处理每一位的贡献wa了

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline int gcd(int a, int b) {
    return b==0?a:gcd(b,a%b);
}
inline int lcm(int a, int b) {
    return a*b/gcd(a, b);
}

const int N = 2520; // lcm(1..9)

int mp[N+1], a[20];
ll dp[20][N][50];
int f[20][10];

inline ll dfs(int pos, int sum, int nlcm, bool limit) {
    if(pos < 0) return sum % nlcm == 0;
    if(!limit && dp[pos][sum][mp[nlcm]] != -1) return dp[pos][sum][mp[nlcm]];
    int up = limit? a[pos]: 9;
    ll res = 0;
    for (int i = 0; i <= up; i++) {
        if(i == 0) res += dfs(pos-1, sum * 10 % N, nlcm, limit&&i==up);
        else res += dfs(pos-1, (sum*10 + i) % N, lcm(nlcm, i), limit&&i==up);
    }
    if(limit) return res;
    return dp[pos][sum][mp[nlcm]] = res;
}

inline ll solve(ll x) {
    int pos = 0;
    while(x) a[pos++] = x % 10, x /= 10;
    return dfs(pos-1, 0, 1, 1);
}

int main() {
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    memset(dp, -1, sizeof(dp));
    int cnt = 0;
    for (int i = 1; i <= N; i++)
        if(N % i == 0) mp[i] = ++cnt;

    int T;
    cin >> T;
    while (T--) {
        ll l, r;
        cin >> l >> r;
        cout << solve(r) - solve(l-1) << endl;
    }
}

HDU - 4734

为避免初始化造成TLE,要将dp数组开为dp[pos][fx],表示当位置为pos、剩余带权和为fx的数字数量。

这样,对多组测试,他们解决的仍然是重复的子问题,有点逆向思维的味道。

注意带权和的转移方式!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int fa = 0, a[20], dp[20][5000];
int dfs(int pos, int limit, int fx) {
    if(pos < 0) return 1;
    if(!limit && dp[pos][fx] != -1) return dp[pos][fx];
    int res = 0;
    int up = limit? a[pos]: 9;
    for (int i = 0; i <= up; i++) {
        if(fx < i * (1<<pos)) break;
        res += dfs(pos-1, limit&&i==up, fx-i*(1<<pos));
    }
    if(limit) return res;
    return dp[pos][fx] = res;
}
int solve(int x) {
    int pos = 0;
    while(x) a[pos++] = x % 10, x /= 10;
    return dfs(pos-1, 1, fa);
}
int main() {
    memset(dp, -1, sizeof(dp));
    int t, kase = 0;
    scanf("%d", &t);
    while (t--) {
        int a, b;
        fa = 0;
        scanf("%d %d", &a, &b);
        int bit = 1, tmp = a;
        while(tmp) {
            fa += bit * (tmp % 10);
            bit <<= 1;
            tmp /= 10;
        }
        printf("Case #%d: %d
", ++kase, solve(b));
    }
}

HDU - 3709

一个数字如果是balance number,那么它有且仅有一个基准。

外围枚举基准的位置,分情况数位DP,不会有重复计算的问题。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[20];
ll dp[20][20][3000]; // sum开空间考虑极限情况
ll dfs(int pos, bool limit, int pivot, int sum) {
    if(pos < 0) return !sum;
    if(sum < 0) return 0;
    if(!limit && ~dp[pos][pivot][sum]) return dp[pos][pivot][sum];
    ll res = 0;
    int up = limit? a[pos]: 9;
    for (int i = 0; i <= up; i++) {
        res += dfs(pos-1, limit&&i==up, pivot, sum + (pos-pivot) * i);
    }
    if(limit) return res;
    return dp[pos][pivot][sum] = res;
}
ll solve(ll x) {
    int pos = 0;
    while(x) a[pos++] = x % 10, x /= 10;
    ll res = 0;
    for (int i = 0; i < pos; i++) { // 枚举pivot
        res += dfs(pos-1, 1, i, 0);
    }
    return res - pos + 1; // 0只能被计算一次
}
int main() {
    memset(dp, -1, sizeof(dp));
    int T;
    scanf("%d", &T);
    while (T--) {
        ll x, y;
        scanf("%lld %lld", &x, &y);
        printf("%lld
", solve(y) - solve(x-1));
    }
}

SP - 10606

状态用三进制表示

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[20], pow3[10];
ll dp[20][2][60000];

vector<int> get(int state) {
    vector<int> res(10);
    int pos = 0;
    while (state) {
        res[pos++] = state % 3;
        state /= 3;
    }
    return res;
}

int cal(vector<int> vec) {
    int res = 0;
    for (int i = 0; i <= 9; i++) {
        res += vec[i] * pow3[i];
    }
    return res;
}

int update(int state, int digit) {
    vector<int> vec = get(state);
    if(vec[digit] == 2) {
        vec[digit] = 1;
    }
    else {
        ++vec[digit] %= 2;
    }
    return cal(vec);
}

bool isbalanced(int state) {
    vector<int> vec = get(state);
    for (int i = 0; i < 10; i++) {
        if(vec[i] == 2) continue;
        if(i%2==1 && vec[i] != 0) return 0;
        if(i%2==0 && vec[i] != 1) return 0;
    }
    return 1;
}

ll dfs(int pos, bool limit, bool lead, int state) {
    if(pos < 0) return isbalanced(state);
    if(!limit && ~dp[pos][lead][state]) return dp[pos][lead][state];
    
    ll res = 0;
    int up = limit? a[pos]: 9;
    for (int i = 0; i <= up; i++) {
        if(lead && !i) {
            res += dfs(pos-1, limit&&i==up, 1, state);
        }
        else {
            res += dfs(pos-1, limit&&i==up, 0, update(state, i));
        }
    }
    if(limit) return res;
    return dp[pos][lead][state] = res;
}

ll solve(string s) {
//    cout << s << endl;
    int pos = 0;
    while (!s.empty()) {
        a[pos++] = s.back() - '0';
        s.pop_back();
    }
    return dfs(pos-1, 1, 1, cal(vector<int>(10, 2)));
}

string minusone(string s) {
    int sz = (int)s.size();
    s.back()--;
    for (int i = sz-1; i >= 0; i--) {
        if(s[i] < '0') {
            s[i] += 10;
            s[i-1]--;
        }
        else break;
    }
    return s;
}

int main() {
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    pow3[0] = 1;
    for (int i = 1; i <= 9; i++) pow3[i] = pow3[i-1] * 3;
    memset(dp, -1, sizeof(dp));
    int t;
    cin >> t;
    while (t--) {
        string A, B;
        cin >> A >> B;
        cout << solve(B) - solve(minusone(A)) << endl;
    }
}

HDU - 4352

LIS + 树位DP

设计状态要易于解决LIS问题。

因为最长递增子序列的长度k<=10,且只出现0~9的数字,所以可以用状态压缩表示以pos为结尾、长度为k的最长公共子串的排列state。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[20];
ll dp[20][2][2000][20];
int lower_bound(int state, int digit) {
    for (int i = digit; i <= 9; i++) {
        if(state & (1<<i)) return i;
    }
    return digit;
}
ll dfs(int pos, bool limit, bool lead, int state, int k) {
    if(pos < 0) return !k;
    if(!limit && ~dp[pos][lead][state][k]) return dp[pos][lead][state][k];
    ll res = 0;
    int up = limit? a[pos]: 9;
    int last = -1;
    
    for (int i = 9; i >= 0; i--) {
        if((1<<i) & state) {
            last = i;
            break;
        }
    }
    
    for (int i = 0; i <= up; i++) { // > last
        if(i > last) {
            if(lead&&!i) {
                int new_state = state ^ (1<<i) ^ (1<<(lower_bound(state, i)));
                res += dfs(pos-1, limit&&i==up, 1, new_state, k);
            }
            else {
                res += dfs(pos-1, limit&&i==up, 0, (1<<i) ^ state, k-1);
            }
        }
        else {
            int new_state = state ^ (1<<i) ^ (1<<(lower_bound(state, i)));
            res += dfs(pos-1, limit&&i==up, lead&&!i, new_state, k);
        }
    }
    if(limit) return res;
    return dp[pos][lead][state][k] = res;
}

ll solve(ll x, int k) {
    int pos = 0;
    while (x) a[pos++] = x % 10, x /= 10;
    return dfs(pos-1, 1, 1, 0, k);
}

int main() {
    memset(dp, -1, sizeof(dp));
    int t, kase = 0;
    scanf("%d", &t);
    while (t--) {
        ll l, r; int k;
        scanf("%lld %lld %d", &l, &r, &k);
        printf("Case #%d: %lld
", ++kase, solve(r, k) - solve(l-1, k));
    }
}

HDU - 4507

如何在数位DP的过程中转移平方和?

  • (cnt(y) = sum{cnt(x)})

  • (sum{(y+x)} = sum{(y * cnt(x))} + sum{x})

  • (sum{(y+x)^2} = sum{(y^2*cnt(x))} + 2*sum{x}*y + sum{x^2})

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
int a[20];
struct Node {
    ll segma; // ∑
    ll x2; // x^2
    ll plan;
    Node () {
        plan = 0;
        x2 = 0;
        segma = 0;
    }
    Node (ll _segma, ll _x2, ll _plan) {
        segma = _segma;
        x2 = _x2;
        plan = _plan;
    }
};

Node dp[20][7][7];
ll pow10[20];
Node dfs(int pos, int sum1, int sum2, bool limit) {
    if(pos < 0) return Node(0,0,sum1&&sum2);
    if(!limit && dp[pos][sum1][sum2].segma != 0)
        return dp[pos][sum1][sum2];
    int up = limit? a[pos]: 9;
    Node res;
    for (int i = 0; i <= up; i++) {
        if(i == 7) continue;
        Node t = dfs(pos-1, (sum1*10+i)%7, (sum2+i)%7, limit&&i==up);
        
        ll y = (i * pow10[pos]) % mod;
        
        (res.plan += t.plan) %= mod;
        (res.segma += t.segma  + y * t.plan % mod) %= mod;
        ll k1 = ((y * y % mod) * t.plan) % mod;
        ll k2 = (2 * y) % mod * t.segma % mod;
        (res.x2 += (k1+k2)%mod+t.x2%mod) %= mod;
    }
    if(limit) return res;
    return dp[pos][sum1][sum2] = res;
}

ll solve(ll x) {
    int pos = 0;
    while(x) a[pos++] = x % 10, x /= 10;
    return dfs(pos-1, 0, 0, 1).x2 % mod;
}

int main() {
    pow10[0] = 1;
    for (int i = 1; i <= 18; i++) {
        pow10[i] = pow10[i-1] * 10 % mod;
    }
    int T;
    scanf("%d", &T);
    while (T--) {
        ll l, r;
        scanf("%lld%lld", &l, &r);
        ll ans = (solve(r) - solve(l-1) + mod) % mod;
        printf("%lld
", ans);
    }
}
原文地址:https://www.cnblogs.com/Waldeinsamkeit/p/13444618.html