数位dp对于状态描述与发现的一些感悟
今天刷的数位dp 第一题看了题解以后知道了数位dp的基本板子,写数位dp的方式(运用记忆化递归的方法)已经基本固定。
那么接下来的难点主要还是对于题目描述的问题,如何抽象成dp中的状态。就今天刷的题来看,dp数组第一维一般为第i位数,这是数位dp的一般表示数的方式。而数组究竟还要加几维就要看题目要求的东西。
如下题:
http://acm.hdu.edu.cn/showproblem.php?pid=3709
题意即求:
对于某个 number,你可以 fix a pivot 在某位,然后如果分成的左右两部分的 sigma(d[i] * | i - fixloc |)相等,则它是 Balanced Number。
统计区间 [a,b] 中Balance Number 的个数。(引用https://blog.csdn.net/dgq8211/article/details/9302069)
这个题目问的是满足条件的最大总数。故dp数组(不管几维)=当前状态的最大总数。
那么问题用数位dp 关注数字本身属性的思想,可以抽象出一个状态即支点的位置[x]。
//除此,与问题结果直接相关的即支点两端的力矩差[sum]。
因为数位dp是(貌似dp都是)枚举所有情况再找到最优解的,所以一定会有一个状态为问题所要求满足的东西。以该题看,条件要求满足的就是支点两边的力矩差=0,那么力矩差就一定是dp数组里的一维了。
综上两个状态发现最大总数就是各个点为支点所有满足力矩差为0的情况的总和sum。
可以将所有的情况列出。此时思考dp数组结束,即dp[pos][x][sum]。
找到正确的dp数组后就可以开心的套板子再调整细节等AC了~~~
(这题AC代码可以从上面的链接看,我偷懒不发了)
其实如果能将问题抽象为数学函数那么能更加直观的发现状态,如题目要求找到(0,n)上所有能被13整除的数的总数那满足要求的x=cigma(n%13==0) dp数组即cigma(),状态就是数n和n%13,(也就是自变量和与自变量相关的运算)。
可得dp[pos][13]。
#include<cstdio> #include<cstring> int dp[10][15]; int digit[10]; int n; int dfs(int pos,int mod ,int limit) { if(pos==-1)return mod==0; if(!limit&&dp[pos][mod]!=-1)return dp[pos][mod]; int tmp=0,mm,; int up=limit?digit[pos]:9; for(int i=0;i<=up;i++) { mm=(mod*10+i)%13; tmp+=dfs(pos-1,mm,limit&&i==digit[pos]); } if(!limit)dp[pos][mod]=tmp; return tmp; } int solve(int x) { int k = x; int pos = 0; while(k) { digit[pos++]=k%10; k/=10; } return dfs(pos-1,0,1); } int main() { memset(dp,-1,sizeof(dp)); while(~scanf("%d",&n)) { printf("%d\n",solve(n)); } return 0; }