Let life be beautiful l|

Ginger_he

园龄:3年1个月粉丝:5关注:5

数位dp学习笔记

1 简介

适用题型

  • 不关注数的大小;
  • 经过转化后可以运用与数位有关的思想;
  • 常见问法:在 [l,r] 中,有多少个数满足 f(i)

方法
我们通常使用前缀和的思想和记忆化搜索来实现数位dp。

2 常见题型

模板
就是上面所说的常见问法。
例题:windy 数

int dfs(int k,int x,int p,int q)
{
    if(!k)
        return 1;
    if(!p&&!q&&f[k][x]!=-1)
	return f[k][x];
    int res=0,y=q?a[k]:9;
    for(int i=0;i<=y;i++)
    {
        if(abs(i-x)<2)
            continue;
	if(p&&!i) res+=dfs(k-1,-2,1,q&&(i==y));
        else res+=dfs(k-1,i,0,q&&(i==y));
    }
    if(!p&&!q)
        f[k][x]=res;
    return res;
}

这道题的难点就在于对相邻两个数字之差至少为 2 的处理,我们一开始将 x 设为 2,后面只需要判断前导 0 即可。

单独判断左端点
这类题目的数据范围通常会很大,lr 需要用数组存,这时候就需要用 cnt(1,r)cnt(1,l),再单独判断 l 是否符合条件。
例题:Magic Numbers

ll dfs(int k,int x,int p)
{
    if(k>len)
        return x?0:1;
    if(!p&&f[k][x]!=-1)
        return f[k][x];
    int y=p?a[k]:9;
    ll res=0;
    if(k&1)
    {
        for(int i=0;i<=y;i++)
        {
            if(i==d)
                continue;
            res=(res+dfs(k+1,(x*10+i)%m,p&&(i==y)))%mod; 
        }
    }
    else
    {
        if(d<=y)
            res=(res+dfs(k+1,(x*10+d)%m,p&&(d==y)))%mod;
    }
    if(!p)
        f[k][x]=res;
    return res;
}

这道题就是多加了被 m 整除这个条件,我们在 dfs 中用 x 表示模 m 的余数,每次变为 (x×10+i)modm,最后判断是否为 0 即可。

bool check(char *s)
{
    len=strlen(s+1);
    int x=0;
    for(int i=1;i<=len;i++)
    {
        int y=s[i]-'0';
        x=(x*10+y)%m;
        if(i&1)
        {
            if(y==d)
                return false;
        }
        else
        {
            if(y!=d)
                return false;
        }
    }
    return !x;
}

这里顺便把 check 函数也放上来了。

统计数量以外的元素
这类题目通常问的不是符合条件的数的数量,而是问符合条件的数的和等,这时候用结构体进行记忆化即可。(这类题目对数学好的选手比较友好,因为通常需要推式子)
例题:恨 7 不成妻

node dfs(int k,int x,int y,int q)
{
    if(!k)
        return node{x&&y,0,0};
    if(!q&&~f[k][x][y].u)
	return f[k][x][y];
    int z=q?a[k]:9;
    node res={0,0,0},tmp;
    for(int i=0;i<=z;i++)
    {
	if(i==7)
	    continue;
	tmp=dfs(k-1,(x+i)%7,(y*10+i)%7,q&&(i==z));
	res.u=(res.u+tmp.u)%p;
	res.v=(res.v+tmp.v+i*v[k-1]%p*tmp.u%p)%p;
	res.w=(res.w+tmp.w+i*i%p*v[k-1]%p*v[k-1]%p*tmp.u%p+i*v[k-1]%p*tmp.v%p*2%p)%p;
    }
    if(!q)
	f[k][x][y]=res;
    return res;
}

与状压结合
这类题目通常与数字出现的次数有关,这时候我们用 sta 的二进制来进行相应的表示即可。
例题:Balanced Numbers

ll check(int x,int y)
{
    for(int i=0;i<=9;i++)
    {
	if(y&(1<<i))
	{
	    if((i&1)^(x&(1<<i))==0)
	        return 0; 
	}
    }
    return 1;
}
ll dfs(int k,int x,int y,int p,int q)
{
    if(!k)
	return check(x,y);
    if(!p&&!q&&f[k][x][y]!=-1)
	return f[k][x][y];
    int z=q?a[k]:9,w;
    ll res=0;
    for(int i=0;i<=z;i++)
    {
	w=p&&!i;
	res+=dfs(k-1,w?0:x^(1<<i),w?0:y|(1<<i),w,q&&(i==z));
    }
    if(!p&&!q)
	f[k][x][y]=res;
    return res;
}

注意表示出现次数的 x 用异或转移,表示是否出现过的 y 用或转移。

3 写在最后

根据个人经验,要想完全掌握数位dp,最重要的是多练习,这里推荐一份题单

本文作者:Ginger_he

本文链接:https://www.cnblogs.com/Gingerhe/p/15847534.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Ginger_he  阅读(68)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起