zoukankan      html  css  js  c++  java
  • 数位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);
        }
    }
    
  • 相关阅读:
    LeetCode-167-两数之和 II
    LeetCode-160-相交链表
    LeetCode-155-最小栈
    [leetcode]7. Reverse Integer反转整数
    [leetcode]4. Median of Two Sorted Arrays俩有序数组的中位数
    [leetcode]2. Add Two Numbers两数相加
    [leetcode]210. Course Schedule II课程表II
    Topological Sorting拓扑排序
    [leetcode]62. Unique Paths 不同路径(求路径和)
    [leetcode]387. First Unique Character in a String第一个不重复字母
  • 原文地址:https://www.cnblogs.com/Waldeinsamkeit/p/13444618.html
Copyright © 2011-2022 走看看