zoukankan      html  css  js  c++  java
  • 数位dp

    https://blog.csdn.net/wust_zzwh/article/details/52100392 

    讲的好的博客。

    https://cn.vjudge.net/contest/302933#problem/B

    这道题要用减法,

    前导零不排除的模板

    题目给了个f(x)的定义:F(x) = An * 2n-1 + An-1 * 2n-2 + ... + A2 * 2 + A1 * 1,Ai是十进制数位,然后给出a,b求区间[0,b]内满足f(i)<=f(a)的i的个数。
    常规想:这个f(x)计算就和数位计算是一样的,就是加了权值,所以dp[pos][sum],这状态是基本的。a是题目给定的,f(a)是变化的不过f(a)最大好像是4600的样子。如果要memset优化就要加一维存f(a)的不同取值,那就是dp[10][4600][4600],这显然不合法。
    这个时候就要用减法了,dp[pos][sum],sum不是存当前枚举的数的前缀和(加权的),而是枚举到当前pos位,后面还需要凑sum的权值和的个数,
    也就是说初始的是时候sum是f(a),枚举一位就减去这一位在计算f(i)的权值,那么最后枚举完所有位 sum>=0时就是满足的,后面的位数凑足sum位就可以了。
    仔细想想这个状态是与f(a)无关的(新手似乎很难理解),一个状态只有在sum>=0时才满足,如果我们按常规的思想求f(i)的话,那么最后sum>=f(a)才是满足的条件。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    
    using namespace std;
    
    const int N = 12;
    int dp[N][5000];
    int pw[N];
    int num[N];
    int ans;
    int ca = 0;
    
    void init() {
        pw[0] = 1;
        for(int i = 1; i < 12; i++) {
            pw[i] = pw[i - 1] * 2;
        }
    }
    
    int dfs(int pos, int sum, bool lim) {
        if(pos == -1) {
            return ans - sum >= 0;
        }
        if(sum > ans) return 0;
        int up = lim ? num[pos] : 9;
        int res = 0;
        if(!lim && dp[pos][ans - sum] != -1) return dp[pos][ans - sum];
        for(int i = 0; i <= up; i++) {
            res += dfs(pos - 1, sum + i * pw[pos], lim && i == num[pos]);
        }
        if(!lim) dp[pos][ans - sum] = res;
        return res;
    }
    
    void solve(int n, int m) {
        ans = 0;
        int cnt = 0;
        while(n) {
            ans += (n % 10) * pw[cnt++];
    //        printf("%d %d
    ", pw[cnt - 1], n % 10);
            n /= 10;
        }
    //    printf("%d
    ", ans);
        cnt = 0;
        while(m) {
            num[cnt++] = m % 10;
            m /= 10;
        }
        printf("Case #%d: %d
    ", ++ca, dfs(cnt - 1, 0, 1));
    }
    
    int t;
    int n, m;
    int main() {
        scanf("%d", &t);
        memset(dp, -1, sizeof(dp));
        init();
        while(t--) {
            scanf("%d%d", &n, &m);
            solve(n, m);
        }
        return 0;
    
    }

    https://cn.vjudge.net/contest/302933#problem/C

    前导零排除的模板

    找区间中有多少个二进制中0的个数大于1的个数的数。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    
    using namespace std;
    
    const int N = 35;
    int dp[N][70];
    int a[N];
    
    int dfs(int pos, int sta, int lead, int lim) {
        if(pos == -1) return sta >= 35;
        int ans = 0;
        if(!lim && !lead && dp[pos][sta] != -1) return dp[pos][sta];
        int up = lim ? a[pos] : 1;
        for(int i = 0; i <= up; i++) {
      //如果前面全是零的话,这个零就不需要算
    if(lead && i == 0) ans += dfs(pos - 1, sta, 1, lim && i == a[pos]); else ans += dfs(pos - 1, sta + (i == 0 ? 1 : -1), 0, lim && i == a[pos]); } if(!lim && !lead) dp[pos][sta] = ans; return ans; } int solve(int x) { int cnt = 0; while(x) { a[cnt++] = x & 1; x >>= 1; } return dfs(cnt - 1, N, 1, 1); } int n, m; int main() { memset(dp, -1, sizeof(dp)); while(~scanf("%d%d", &n, &m)) { printf("%d ", solve(m) - solve(n - 1)); } return 0; }

    https://ac.nowcoder.com/acm/contest/887/H

    给出A,B,C,然后求有多少组0-A的数与0-B的数满足x & y > c, x ^ y < c.

    数位dp

    解法:首先与和异或都不可能导致进位,所以可以一位一位的比,如果高位的大那个后面无论怎样肯定都是大的,小的话同理。

    数位dp的复杂度为dp数组的总维度乘以一次dfs里的循环数。

    这一题把lim也加进了dp数组,因为这一题边界情况可能不止访问一次,因为他有两个数,以前基本上边界都只会访问一次,所以不必要保存状态,而这个题目边界会访问多次,

    所以需要保存状态。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 35;
    
    int numa[N], numb[N], numc[N];
    ll dp[35][3][3][2][2];
    ll dfs(int pos, int ca, int cb, int lima, int limb) {
        if(pos == -1) return ca == 2 || cb == 0;
        if(ca == 0 && cb == 2)   return 0;
        if(dp[pos][ca][cb][lima][limb] != -1) return dp[pos][ca][cb][lima][limb];
        int ea = lima ? numa[pos] : 1;
        int eb = limb ? numb[pos] : 1;
        ll ans = 0;
        for(int i = 0; i <= ea; i++) {
            for(int j = 0; j <= eb; j++) {
                int x = i & j;
                int y = i ^ j;
                int cca = ca, ccb = cb;
                if(ca == 1) {
                    if(x == numc[pos]) cca = 1;
                    else if(x < numc[pos]) cca = 0;
                    else cca = 2;
                }
                if(cb == 1) {
                    if(y == numc[pos]) ccb = 1;
                    else if(y < numc[pos]) ccb = 0;
                    else ccb = 2;
                }
                ans += dfs(pos - 1, cca, ccb, lima && i == ea, limb && j == eb);
            }
        }
        dp[pos][ca][cb][lima][limb] = ans;
        return ans;
    }
    
    ll solve(ll a, ll b, ll c) {
        memset(numa, 0, sizeof(numa));
        memset(numb, 0, sizeof(numb));
        memset(numc, 0, sizeof(numc));
        ll aa = a, bb = b, cc = c;
        int cnta = 0, cntb = 0, cntc = 0;
        while(a) numa[cnta++] = a % 2, a /= 2;
        while(b) numb[cntb++] = b % 2, b /= 2;
        while(c) numc[cntc++] = c % 2, c /= 2;
        int ma = max(cnta, max(cntb, cntc));
        ll ans = dfs(ma - 1, 1, 1, 1, 1);
      //这里是排除0的情况。
    return ans - min(bb, cc - 1) - min(aa, cc - 1) - 1; } int main() { int t; scanf("%d", &t); ll a, b, c; while(t--) { scanf("%lld%lld%lld", &a, &b, &c); memset(dp, -1, sizeof(dp)); printf("%lld ", solve(a, b, c)); } return 0; }
  • 相关阅读:
    django大全
    centos 下安装python3.6.2
    爬虫基础知识与简单爬虫实现
    HDU5950 Recursive sequence (矩阵快速幂加速递推) (2016ACM/ICPC亚洲赛区沈阳站 Problem C)
    ZOJ5833 Tournament(递归打表)
    ZOJ4067 Books(贪心)
    ZOJ4062 Plants vs. Zombies(二分+贪心)
    ZOJ4060 Flippy Sequence(思维题)
    洛谷P2568 GCD(线性筛法)
    2018.11.6刷题记录
  • 原文地址:https://www.cnblogs.com/downrainsun/p/10908993.html
Copyright © 2011-2022 走看看