zoukankan      html  css  js  c++  java
  • 关于数位DP的学习

    ---恢复内容开始---

    因为最近做比赛经常会出现数位DP,便尝试着去学学看数位DP。

    先给出两篇论文的链接:

    数位计数问题解法研究

    浅谈数位类统计问题

    然后也是寻找了很多大牛的博客,学习了很多(但是没学会囧。),现在先总结一下已经学到的东西

    “在信息学竞赛中,有这样一类问题:求给定区间中,满足给定条件的某个D 进制数或
    此类数的数量。所求的限定条件往往与数位有关,例如数位之和、指定数码个数、数的大小
    顺序分组等等。题目给定的区间往往很大,无法采用朴素的方法求解。此时,我们就需要利
    用数位的性质,设计log(n)级别复杂度的算法。解决这类问题最基本的思想就是“逐位确定”
    的方法。下面就让我们通过几道例题来具体了解一下这类问题及其思考方法。”——刘聪

    事实上,为什么会想到用数位DP来做,就是因为限定条件往往和数位有关,而仔细地朴素的暴力方法中,所做的重复的工作太多。这样的条件会使得DP(记忆化搜索)有用武之地。

    目前我所接触到的大多数的题,都是可以通过记录某些值(比如数位等)来减少重复的运算。当然,因为此类题的特殊性,可以编写check函数确定代码的正确性。

    再偷用某个大牛的一句话:其实数位DP(或者说所有记忆化搜索)都是可以看做通过搜索来填满状态的值。

    首先先想想数位DP的运行模式

    如果我们要统计[0,54321]中满足某个条件的个数,需要将其拆分为

    [00000,09999][10000,19999],[20000,29999],[30000,39999],[40000,49999],

    [50000,50999],[51000,51999],[52000,52999],[53000,53999],

    [54000,54099],[54100,54199],[54200,54299],

    [54300,54309],[54310,54319],

    [54320,54321]

    为什么要这么分呢?随便举个例子,如果我们统计过了[0000,9999]中的满足条件(或者其他各种不满足条件的状态)的个数,那么分别在加上前缀,就可以判断出有多少个满足条件的个数。目的是为了将大的区间划分为小的区间进行求解。

    因此,总结一句话,数位DP减少的运算量为:前面几位固定,后面几位可以任意取的个数统计。

    比如分析一道简单题:HDU 3652,通过我这个渣渣的错误历程来分析一些细节上的问题

    先贴错误代码

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 int dp[20][20][20];
     6 int num[20];
     7 int dfs(int pos,int mod,int pre,int stat,int limit){
     8     if(pos==0) return mod==0&&stat;
     9     if(!limit&&dp[pos][mod][pre]!=-1) return dp[pos][mod][pre];
    10     int ans=0;
    11     int end=limit?num[pos]:9;
    12     for(int i=0;i<=end;i++){
    13         int nmod=(mod*10+i)%13;
    14         int nstat=(pre==1&&i==3)||stat;
    15         ans+=dfs(pos-1,nmod,i,nstat,limit&&i==end);
    16     }
    17     if(!limit) dp[pos][mod][pre]=ans;
    18     return ans;
    19 }
    20 int cal(int x){
    21     int cnt=0;
    22     memset(num,0,sizeof(num));
    23     while(x){
    24         num[++cnt]=x%10;
    25         x/=10;
    26     }
    27     return dfs(cnt,0,0,0,1);
    28 }
    29 int main()
    30 {
    31     int i,j;
    32     int n;
    33     memset(dp,-1,sizeof(dp));
    34     while(scanf("%d",&n)!=EOF){
    35         int ans=cal(n);
    36         printf("%d
    ",ans);
    37     }
    38     return 0;
    39 }

    我选取的记录参数有三个:pos当前处理位,mod前缀和余数,还有前一位的数字pre

    但是运算结果却始终会小于等于正确的答案,为什么呢?

    想了想,发现其实是因为参数含义的问题。

    分析一下,如果我将pre作为一个关键参数记录下来,其实我并不能区分我记录的是后面几位能不能随机取的个数。

    即当下一次搜索到pos,mod,pre的时候,不能确定前面是否有13,或者以前搜索的DP[pos][mod][pre]中的数的个数是否有13。

    因此应该把记录的参数改为pos,mod,stat(表示为记录状态,0为不含13,1为只含前一位为1,2为前缀含有13)。

    因此得到下面的AC代码

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    int dp[20][20][3];
    int num[20];
    int dfs(int pos,int mod,int stat,int limit){
        if(pos==0) return mod==0&&stat==2;
        if(!limit&&dp[pos][mod][stat]!=-1) return dp[pos][mod][stat];
        int ans=0;
        int end=limit?num[pos]:9;
        for(int i=0;i<=end;i++){
            int nmod=(mod*10+i)%13;
            int nstat;
            if(stat==1&&i==3||stat==2) nstat=2;
             else if(i==1) nstat=1;
              else nstat=0;
            ans+=dfs(pos-1,nmod,nstat,limit&&i==end);
        }
        if(!limit) dp[pos][mod][stat]=ans;
        return ans;
    }
    int cal(int x){
        int cnt=0;
        memset(num,0,sizeof(num));
        while(x){
            num[++cnt]=x%10;
            x/=10;
        }
        return dfs(cnt,0,0,1);
    }
    int main()
    {
        int i,j;
        int n;
        memset(dp,-1,sizeof(dp));
        while(scanf("%d",&n)!=EOF){
            int ans=cal(n);
            printf("%d
    ",ans);
        }
        return 0;
    }
    View Code

    好了讲完了

  • 相关阅读:
    web.config配置数据库连接 【转】
    WEB API 返回类型设置为JSON 【转】
    ASP.NET WebAPI 路由规则与POST数据 【转】
    ASP.NET Web API路由规则(二) 【转】
    七天学会ASP.NET MVC(七)——创建单页应用 【转】
    jumpserver-v0.5.0 应用图解
    jumpserverv0.5.0 基于 CentOS7安装部署
    tar: Removing leading `/' from member names
    redis cli命令
    zabbix监控redis的key值
  • 原文地址:https://www.cnblogs.com/acalvin/p/3760082.html
Copyright © 2011-2022 走看看