数位DP

Posted ShadowAA

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数位DP相关的知识,希望对你有一定的参考价值。

数位DP

数位是指把一个数字按照个、十、百、千等等一位一位地拆开,关注它每一位上的数字。如果拆的是十进制数,那么每一位数字都是 0~9,其他进制可类比十进制。

数位 DP:用来解决一类特定问题,这种问题比较好辨认,一般具有这几个特征:

  1. 要求统计满足一定条件的数的数量(即,最终目的为计数);
  2. 这些条件经过转化后可以使用「数位」的思想去理解和判断;
  3. 输入会提供一个数字区间(有时也只提供上界)来作为统计的限制;
  4. 上界很大(比如 ),暴力枚举验证会超时。

例题

不要62

https://loj.ac/p/10167

#include<bits/stdc++.h>
using namespace std;
int n,m,a[100],f[100][3];
int sc(int len,int t,int ff)

	int end,i;
	if (len==0)
		return 1;
	if ((ff)and(f[len][t]!=-1))
		return f[len][t];
	ff==false?end=a[len]:end=9;
	int s=0;
	for (i=0;i<=end;i++)
	
		if (i==4) continue;
		if (i==6)
			s=s+sc(len-1,1,ff or i<a[len]);
		else
			if (!((i==2)and(t==1)))
				s=s+sc(len-1,0,ff or i<a[len]);
	
	if (ff)
		f[len][t]=s;
	return s;

int solve(int x)

	int len=0;
	while (x>0)
	
		a[++len]=x%10;
		x=x/10;
	
	memset(f,-1,sizeof(f));
	return sc(len,0,0);

int main()

	while (cin>>n>>m)
	
		if ((n==0)and(m==0))
			break;
		cout<<solve(m)-solve(n-1)<<endl;
	

数字计数

统计n到m中所有数位出现的次数

https://loj.ac/p/10169

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m,i,g[20],c[20],f[100][2][20],a[100];
ll ff[20];
void build()

	ll x=1;
	for (int i=0;i<=12;i++)
	
		ff[i]=x;
		x=x*10;
	

void sc(int len,int t,int w,ll x)

	if (len==0)
		return;
	if (t and(f[len][w][0]!=-1))
	
		for (int i=0;i<=9;i++)
			g[i]=g[i]+f[len][w][i];
		return;
	
	int end,i;ll s=0;
	t==0?end=a[len]:end=9;
	ll b[10];
	for (i=0;i<=9;i++)
		b[i]=g[i];
	for (i=0;i<=end;i++)
	
		sc(len-1,t or i<a[len],w or i>0,x);
		if (!((w==0)and(i==0))or(len==1))
		
			if (t or i<a[len])
				g[i]=g[i]+ff[len-1];
			else g[i]=g[i]+x%ff[len-1]+1;
		
	
	if (t)
		for (i=0;i<=9;i++)
			f[len][w][i]=g[i]-b[i];

void solve(ll x)

	ll len=0,y=x;
	while (x>0)
	
		a[++len]=x%10;
		x=x/10;
	
	memset(g,0,sizeof(g));
	if (len==0) g[0]=1;
	else sc(len,0,0,y);

int main()

	cin>>n>>m;
	build();
	memset(f,-1,sizeof(f));
	solve(m);
	for (i=0;i<=9;i++)
		c[i]=g[i];
	solve(n-1);
	for (i=0;i<=8;i++)
		cout<<c[i]-g[i]<<\' \';
	cout<<c[9]-g[9]<<endl;

恨7不是妻

https://vjudge.net/problem/LibreOJ-10168

题解:https://www.cnblogs.com/graytido/p/12202754.html

#include <bits/stdc++.h>
using namespace std;
/*    freopen("k.in", "r", stdin);
    freopen("k.out", "w", stdout); */
//clock_t c1 = clock();
//std::cerr << "Time:" << clock() - c1 <<"ms" << std::endl;
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#define de(a) cout << #a << " = " << a << endl
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, a, n) for (int i = n; i >= a; i--)
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef vector<int, int> VII;
#define inf 0x3f3f3f3f
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll MAXN = 1e6 + 7;
const ll MAXM = 1e6 + 7;
const ll MOD = 1e9 + 7;
const double eps = 1e-6;
const double pi = acos(-1.0);
int a[105];
/*   1、整数中某一位是7;
  2、整数的每一位加起来的和是7的整数倍;
  3、这个整数是7的整数倍; */
struct node

    ll sum;   //与7无关的数的个数
    ll qsum;  //与7无关的数和
    ll sqsum; //ans
    node(ll _sum = -1, ll _qsum = 0, ll _sqsum = 0)  sum = _sum, qsum = _qsum, sqsum = _sqsum; 
;
node dp[30][15][15];
ll c[20];
node dfs(int pos, int sta1, int sta2, bool lim) //sta1各位数和%7 sta2前面%7

    if (pos < 0)
        return node(sta1 && sta2, 0, 0);
    if (!lim && dp[pos][sta1][sta2].sum != -1)
        return dp[pos][sta1][sta2];
    int up = lim ? a[pos] : 9;
    node ret = node(0, 0, 0);
    for (int i = 0; i <= up; i++)
    
        if (i != 7)
        
            node t = dfs(pos - 1, (sta1 + i) % 7, (sta2 * 10 + i) % 7, lim && i == a[pos]);
            ret.sum += t.sum;
            ret.sum %= MOD;
            ret.qsum += (((c[pos] * i % MOD) * t.sum % MOD) + t.qsum) % MOD;
            ret.qsum %= MOD;
            ret.sqsum += t.sqsum % MOD;
            ret.sqsum %= MOD;
            ret.sqsum += ((i * i * c[pos] % MOD) * c[pos] % MOD) * t.sum % MOD;
            ret.sqsum %= MOD;
            ret.sqsum += ((i * 2 * c[pos] % MOD) * t.qsum) % MOD;
            ret.sqsum %= MOD;
        
    
    if (!lim)
        dp[pos][sta1][sta2] = ret;
    return ret;

ll solve(ll x)

    int pos = -1;
    while (x)
    
        a[++pos] = x % 10;
        x /= 10;
    
    return dfs(pos, 0, 0, true).sqsum;

void init()

    c[0] = 1;
    for (int i = 1; i < 20; i++)
        c[i] = (c[i - 1] * 10) % MOD;


int main()

    int t;
    init();
    scanf("%d", &t);
    while (t--)
    
        ll L, R;
        scanf("%lld%lld", &L, &R);
        printf("%lld\\n", ((solve(R) - solve(L - 1)) % MOD + MOD) % MOD);
    
    return 0;

数位dp

数位dp

定义

数位dp(Digit Entry DP)是一种计数用的dp,一般就是要哦统计区间[l,r]内满足一些条件的数的个数。所谓数位dp,字面意思就是在数位上进行dp。数位的含义:一个数有个位、十位、百位、千位......数的每一位就是数位啦!

数位dp的思想

数位dp的实质就是换一种暴力枚举的方式,使得新的枚举方式满足dp的性质,然后记忆化就可以了。

模板及例题

模板:

typedef long long ll;
int a[20];
ll dp[20][state];   //不同题目状态不同
ll dfs(int pos,int state,bool lead,bool limit)       //变量,状态,前导0,数位上界;注意不是每题都要判断前导零
{
    if(pos==0) return 1;    //递归边界,一般一种递归到结束只能产生一种情况
    if(!limit && !lead && dp[pos][state]!=-1) return dp[pos][state];    //记忆化
    int up=limit?a[pos]:9;  //枚举上界
    ll ans=0;               //计数
    for(int i=0;i<=up;i++)  //枚举,然后把不同情况的个数加到ans就可以了
    {
        if() ...
        else if()...        //一下条件
        ans+=dfs(pos-1,/*状态转移*/,lead && i==0,limit && i==a[pos]) //最后两个变量传参都是这样写的
        //state状态转移要保证i的合法性,比如不能有62,那么当pre==6&&i==2就不合法,这里用state记录pre是否为6即可。
    }
    if(!limit && !lead) dp[pos][state]=ans;
    return ans;
}
ll solve(ll x)
{
    int tot=0;
    while(x)
    {
        a[++tot]=x%10;
        x/=10;
    }
    return dfs(tot/*从最高位开始枚举*/,/*一系列状态 */,true,true);//刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛
}
int main()
{
    ll le,ri;
    while(~scanf("%lld%lld",&le,&ri))
    {
        //初始化dp数组为-1,这里还有更加优美的优化,后面讲
        printf("%lld
",solve(ri)-solve(le-1));
    }
}

例题1:【不要62】(数位dp入门经典题)

描述:给定一个区间,不带4以及没有连续62的数字有多少个;

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
ll dp[20][2],arr[20];
ll dfs(int pos,int state,bool lead,bool limit)
{
    if(pos==0)  return 1;
    if(!limit && !lead && dp[pos][state]!=-1)   return dp[pos][state];
    int up = limit?arr[pos]:9;
    ll ans = 0;
    for(int i=0;i<=up;++i)
    {
        if(i==4)    continue;
        if(state && i==2)   continue;
        ans += dfs(pos-1,i == 6,lead && i==0,limit && i==arr[pos]);
    }
    if(!limit && !lead) dp[pos][state] = ans;
    return ans;
}
ll solve(int x)
{
    int tot = 0;
    while (x){
        arr[++tot]=x%10;
        x/=10;
    }
    return  dfs(tot,0,true,true); 
}

int main()
{
    int l,r;
    while(scanf("%d %d",&l, &r) && (l||r))
    {
        memset(dp,-1,sizeof(dp));
        printf("%lld
",solve(r) - solve(l-1));
    }
    system("pasue");
}

以上是关于数位DP的主要内容,如果未能解决你的问题,请参考以下文章

数位dp模板 [dp][数位dp]

数位DP入门

数位dp详解

数位dp总结 之 从入门到模板

动态规划专题——数位DP

数位dp小练