数位dp 笔记
数位dp 笔记
数位dp一直是我的弱项,惦记好久了,最近补了补,感觉还行。
解决的问题 & 主体思想
解决一个区间中,满足某些条件(与每一位有关)的数的数量(或者带权的和)。
做法:考虑求前缀 \([1,x]\) 的答案。
如果你是新手,请先考虑一下大概要怎么做,再继续看
先把位(不一定是十进制)拆开来,然后是一个形如 "dp到第 \(i\) 位,..." 的 dp,一般可以用记忆化搜索,一位一位的填,使得它看起来友好一些(个人感觉这样可读性好)。
然后要解决 \(\le x\) 的限制。每次按照这样的规则来填数字:
- 默认每一位都不能超过 \(x\) 对应的位
- 如果有一位小于了 \(x\) 对应的位,则后面就没限制了
这个很好理解。比如说现在钦点下来是 \(123***\),\(x=123456\),那后面显然不能超过 \(456\)。
而如果现在钦点下来是 \(122***\),那它就算是 \(122999\),也不会超过 \(x\)。
update 2021.05.03
这其实就是数位dp的核心思想:当前填了若干个数,后面还有一堆东西要填。而dp状态就是把当前填好的数,所具备的特征 记录下来。我们并不记下具体填了什么(那样就是暴力了),而只记下我们所关心的特征。而这些特征要视题目而定。
换句话说,我们可以用一张(生草警告)图来描述数位dp:
用一个 lim
标记维护当前是否卡到上界。注意它应该被记在 dp 状态里。
入门 —— windy数
求区间满足:任意相邻两位的差都不超过 \(2\) 的数,的数量。
dp 状态:到第几位,当前选了什么(以决定下一个可不可以选),lim
然后每次 dfs 扩展的时候,判断一下下一个填的是否合法,再加个记忆化,就行了。
绕一个弯 —— 萌数
求区间满足:将数看成字符串,没有任何长度 \(\ge 2\) 的回文串的数,的数量。
没有任何长度 \(\ge 2\) 回文串 \(\rightarrow\) 任意一个字符和它前面一个,两个都不同。
这样就保证了没有长度等于 \(2,3\) 的回文串,然后其余的回文串都是由这两种扩展出来的,自然也没有了。
剩下就很好 dp 了,和上一个差不多。
the end? —— 恨7不成妻
hdu的题,我第一次学数位dp的时候被老师称作“毕业题”
你要能把这个题写出来,你数位dp就差不多了
当时看着老师标程打的,现在简单复习了一下,发现还挺好想的 然后把它秒了,其实就是一个傻逼缝合怪题
要满足三个条件:
- 不能有数位7
- 数位和不能是7的倍数
- 数本身不能是7的倍数
区间求满足条件平方和。
这里涉及到一个带权求和。带权求和状态要变一下,表示从这位开始截取,的带权和。
比如说 \(x=123\),填好了 \(11*\),满足条件的数有 \(111\),\(113\),\(114\),\(116\),\(118\)
带权和为 \(1^2+3^2+4^2+6^2+8^2=126\)
为什么要做一步截取呢?因为要方便转移。考虑转移,相当于,我先确定好后面若干位,在它们的前面都填上相同的数字(这里相当于放上了 \(1\))
然后填相同的数字可以看做是加法 (这里相当于 \(+10\))
然后平方和,整体加,好做吧:再维护数量和一次方和,设为 dp[...][0/1/2]
,对应数量,和,平方和
设现在整体加的为 \(a\),后面一位的 dp[...][0/1/2]
记下来为 nex[0/1/2]
,现在的是 cur[0/1/2]
,则有:
cur[0]+=nex[0];
cur[1]+=nex[1]+nex[0]*a;
cur[2]+=nex[2]+2*nex[1]*a+nex[0]*a*a
(就是拆括号搞一下就行)
对于条件:
- 每次不填 \(7\)
- 记录数位和对 \(7\) 的余数,放在状态里,取 \(0\) 那个状态
- 记录整个数对 \(7\) 的余数,放在状态里,取 \(0\) 那个状态
小心细节 [SDOI2016]储能表
求 \(\sum\limits_{i=0}^{n-1} \sum\limits_{j=0}^{m-1} \max(i\oplus j-k,0)\)
\(n,m,k\le 10^{18}\)
后面等价成 \(>k\) 的和,减去 \(>k\) 的数量乘以 \(k\)
拆成二进制,做数位 \(dp\)。记下三个 lim
,表示是否卡在 \(n\) 的上界,\(m\) 的上界,\(k\) 的下界 (因为 \(k\) 那边是个 \(>\) 的限制)
然后上一题类似的求一下带权和就行了,要维护一下数量和总和。
复杂度起飞 [AHOI2009]同类分布
由于数位 dp 的基本模型只有一个 log,所以可以带很多别的
题意:求区间能整除数位和的数的数量
比如 \(12\) 就满足条件因为 \(1+2\) 是 \(12\) 的因数
\(x\le 10^{18}\)
注意到数位和不会超过 \(9\times 18=162\)
先枚举数位和 \(k\),然后 dp 里设两维,一维表示当前数位和,一维表示当前数模 \(k\) 的余数。最后取答案就是 \(\%k=0\),数位和 \(=k\) 的那个状态
复杂度是 \((9\times \log n)^3\log n\),非常暴力
转换一波 CF908G New Year and Original Order
update: 2021.08.04 新增了这一部分
我们求所有数字排序之后形成的数字,的和。做了前面几个题,脑子里大概有了一个套路,然后就会这么想:
这个排序,我能不能把它拆成贡献的形式?
注意到一个有序的数字,我们可以对它做个差分,然后乘上若干个全“1”数(这个全“1”数相当于是在做前缀和)
比如 114514
排序后变成 111445
,它可以写成:1*111111+3*111+1*1
前面系数 \(1,3,1\) 的意义就是差分。那差分能写成锤子的贡献形式啊草
冷静思考一波,我们发现它还有更多意义
对于每一个数码 \(d\in[1,9]\),我们看有多少个 \(\ge d\) 的,假设有 \(c\) 个。就以114514为例,如下表:
d | c |
---|---|
1 | 6 |
2 | 3 |
3 | 3 |
4 | 3 |
5 | 1 |
\(\ge\) 6 | 0 |
设 \(I(k)\) 表示 “k个1”,即 \(\dfrac{(10^k-1)}{9}\)。我们发现,这个表格里的 \(c\) 出现几次,上面那个差分式子里面的 \(I(c)\) 的系数就是多少。
换句话说,这个东西相当于 \(\sum\limits_{d=1}^{9} I(c)\)。
为啥这样对呢?我们把上面的这些 \(I(c)\) 右对齐,写在一块看看(相当于竖式加法)
111111
111
111
111
1
此时每一列的和,正好就是 \(1,1,1,4,4,5\)。为什么正好对呢?
就比如拿第 \(5\) 列举例,按照排序结果它应该是 \(4\),实际上这一列的和确实是 \(4\),为啥就是 \(4\) 呢,考虑多少个东西算到了它。注意到所有 \(\le 4\) 的 \(d\),它们对应的 \(c\) 都足够长,使得 \(I(c)\) 能贡献到第 \(5\) 列。而其它的 \(d\),它们对应的 \(c\) 恰好都不够长,到不了这一列。
于是这一列被算的次数就是,\([1,9]\) 里面有多少个整数 \(\le 4\)。显然这个数量正好就是 \(4\) 本身。所以是对的。
然后我们的问题就变成,填到某一个位置,\(\ge\) 某个数的有多少个,最后拿这个式子算贡献就行了。具体一点就是设 \(f(i,j,k,lim)\) 表示填到了 \(i\) 位置,\(\ge k\) 的有 \(j\) 个,是否卡上界,方案数。
转移:(枚举新的字符为 \(c\),\(lim\) 的转移略)
答案: