【数位dp】 Step by Step

本文记录了作者逐步学习数位动态规划(DP)的过程,从寻找学习资源开始,通过解决不同难度的题目进行实践,包括水题、中等题和神题。文中详细介绍了HDU 4507、SGU 258和URAL 1057三道中等难度题目的解题思路和代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

看到诸位神牛的代码和Blog,我也来班门弄斧学一下 数位dp

Step0:找木板和资料

向ftiasch 和 edward_mj (窃笑,师父们T_T)求了资料,得到一个好板子——BUPT 某神的Blog

Step1:撸水题

HDU 2089 直接暴力就可以,不过还是老老实实地数位dp一把,基本是板子题目。

HDU 3555 同上的数位DP

UESTC 1307  前导0 建立状态原来还可以用11来代替啊。这样的话每次Quest dfs一次即可。

POJ 3252 f[pos][s][zero][one]

Step2:撸中等题

1.hdu 4507

http://acm.hdu.edu.cn/showproblem.php?pid=4507
Tecent 2012.3.21 C 题

题目描述

求[l , r] 中
 如果一个整数符合下面3个条件之一,那么我们就说这个整数和7有关——
  1、整数中某一位是7;
  2、整数的每一位加起来的和是7的整数倍;
  3、这个整数是7的整数倍;

  现在问题来了:吉哥想知道在一定区间内和7无关的数字的平方和。

方法:

肯定是数位dp
必须这个满足减法——[l , r] = [0 , r] - [0 , l - 1]
我的算法是算相反的——即和数7有关的平方和,然后用1/6 * n * (n + 1) * (2n + 1) 来减一下

首先确定状态 —— dp[枚举位数][是否含有7][个数的和%7][这个数%7]
其次是维护:
1、个数
2、和 (由个数算出
3、平方和(由个数和 和 算出
首先说个数算法:
这就是一个最裸的数位dp,贴个模板就能做了 。。模板含义见资料
再说和的做法:(这里很容易错啊。。。)
在枚举第 i 位 是 d 的时候,算出 i - 1 位之后的个数 has ,那么这个d 就出现了 has 次,于是就要统计 d * 10的幂 * has 。 
最后说平方和的算法:(卧槽这里写挂了好久好么。。。)
还是枚举第 i 位 是 d 的时候。我们看跟后面的关系。假设这个数是 dxxxxxxx(后面一大堆数字) 。假设这个数字是 d * 10的幂(设为 x) + y(后面一大堆数字),那么我们就是要计算(x + y)^2。 拆开来就是 x ^ 2 + 2xy + y ^ 2 。 首先 y ^ 2 在后面我们已经算过了,直接 搜索深度+1就可以计算,2xy 的话需要用 2 * x * (出现次数) * (后面数字的一次幂和), x ^ 2 的话直接 就  x ^ 2 乘以 后面的出现次数。
那么这个题目就解决了。。。

Debug方法:

卧槽数位dp debug 很关键好么。。
我这次的方法很科学,用个暴力的代码算出来比较坑爹的几个数字,比如 7 , 14 , 21 , 100 , 139 的三个部分 —— 个数 , 和, 平方和
然后,剥洋葱皮一样一点儿一点儿展开,先debug最外层的——个数,过掉几个数据之后说明可以信任;然后搞 和最后搞 平方和

Code

我的Code
LL cnt[20][2][7][7] , dp[20][2][7][7] , dpsum[20][2][7][7];
LL ten[20];
LL num[20];
void init(){
    ten[0] = 1;
    REP_1(i , 19) ten[i] = (ten[i - 1] * 10) % MOD;
}
LL dfscnt(int i, bool seven , int numbersum, int sum ,  bool e) {
    if (i==-1) return (seven || numbersum % 7 == 0 || sum % 7 == 0) ? 1 : 0;
    if (!e && ~cnt[i][seven][numbersum][sum]) return cnt[i][seven][numbersum][sum];
    LL res = 0;
    int u = e?num[i]:9;
    for (int d = 0; d <= u; ++d)
        res = ( res + dfscnt(i-1, (seven || d == 7), (numbersum + d) % 7 , (sum * 10 + d) % 7 ,  e&&d==u)) % MOD;
    return e?res:cnt[i][seven][numbersum][sum]=res;
}
LL mul(LL x , LL y){
    x %= MOD;
    y %= MOD;
    return (x * y) % MOD;
}
LL dfssum(int i , int seven , int numbersum , int sum , bool e){
    if (i == -1) return 0;
    if (!e && ~dpsum[i][seven][numbersum][sum]) return dpsum[i][seven][numbersum][sum];
    LL res = 0;
    int u = e ? num[i] : 9;
    for (int d = 0 ; d <= u ; ++d){
        LL has = dfscnt(i-1, (seven || d == 7), (numbersum + d) % 7 , (sum * 10 + d) % 7 ,  e&&d==u);
        LL tmp = mul(d , ten[i]);
        tmp = mul(has , tmp);
        res = (res + dfssum(i-1, (seven || d == 7), (numbersum + d) % 7 , (sum * 10 + d) % 7 ,  e&&d==u)) % MOD;
        res = (res + tmp) % MOD;
    }
    return e ? res : dpsum[i][seven][numbersum][sum] = res ;
}
LL dfs(int i , int seven , int numbersum , int sum , bool e){
    if (i == -1) return 0;
    if (!e && ~dp[i][seven][numbersum][sum]) return dp[i][seven][numbersum][sum];
    LL res = 0;
    int u = e ? num[i] : 9;
    for (int d = 0 ; d <= u ; ++d){
        LL has = dfscnt(i-1, (seven || d == 7), (numbersum + d) % 7 , (sum * 10 + d) % 7 ,  e&&d==u);
        LL sum1 = dfssum(i-1, (seven || d == 7), (numbersum + d) % 7 , (sum * 10 + d) % 7 , e&&d==u);
        LL tmp = mul(d , ten[i]);
        LL nownow = mul(tmp , tmp);
        LL hasnow = mul(nownow , has);
        nownow = mul(2 , mul(tmp , sum1));
        res = (res + dfs(i-1, (seven || d == 7), (numbersum + d) % 7 , (sum * 10 + d) % 7 ,   e&&d==u)) % MOD;
        res = (res + hasnow) % MOD;
        res = (res + nownow) % MOD;
    }
    return e ? res : dp[i][seven][numbersum][sum] = res ;
}
LL l , r;
LL gao(LL x){
    LL a = x , b = x + 1 , c = 2 * x + 1;
    LL p = 2;
    if (a % p == 0) a /= p;
    else if (b % p == 0)  b /= p;
    else if (c % p == 0) c /= p;
    p = 3;
    if (a % p == 0) a /= p;
    else if (b % p == 0)  b /= p;
    else if (c % p == 0) c /= p;
    return mul(mul(a , b) , c);
}
LL getans(LL x){
    if (x == 0) return 0;
    int s = 0;
    LL y = x;
    while(x){
        num[s ++ ] = x % 10;
        x /= 10;
    }
    x = y;
    LL res = gao(x);
    res -= dfs(s - 1 , 0 , 0 , 0 , 1);
    res %= MOD;
    res += MOD;
    res %= MOD;
    return res;
}
void solve(){
    RD(l , r);
    LL ans = getans(r) - getans(l - 1);
    ans %= MOD;
    ans += MOD;
    ans %= MOD;
    printf("%I64d\n" , ans);
}
int main(){
    FLC(dp , -1);
    FLC(cnt , -1);
    FLC(dpsum , -1);
    init();
    Rush solve();
}




AC的(不是很懂。。。)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<string>
#include<iostream>
using namespace std;

typedef long long LL;
typedef double db;
#define mp make_pair
#define pb push_back

const LL P = (int)1e9 + 7;
LL f[20][9 * 20][7][2];//cnt
LL sum1[20][9 * 20][7][2];// sum = 1次和
LL sum2[20][9 * 20][7][2];//sum = 2次和
LL ten[100];

void update(LL &a, LL b) {
	a = (a + b) % P ;
	if(a<0) a+=P;
}

LL MOD(LL a) {
	a %= P;
	return a <0 ? a + P : a;
}

/*debug*/
LL _f[20][9 * 20][7][2];
LL _sum1[20][9 * 20][7][2];
LL _sum2[20][9 * 20][7][2];

void _pre(){
	_f[0][0][0][0]= 1;
	
	for(LL i = 1;i<=6;++i) {
		
		LL lo = 0, hi = ten[i]-1;
		for(LL v = lo; v <= hi; ++ v){
			LL rem = v % 7, dsum = 0;
			LL k = v,msk = 0;
			while(k){
				LL d = k%10;
				if(d == 7) msk = 1;
				dsum += d;k/=10;
			}
			_f[i][dsum][rem][msk]++;
			update(_sum1[i][dsum][rem][msk] , v);
			update(_sum2[i][dsum][rem][msk] , (LL)v*v%P);
		}
	}
	for(LL i = 0; i <= 6; ++ i) {
		LL sumlim = i * 9;

		for(LL sum = 0; sum <= sumlim; ++ sum) {
			for(LL modres = 0; modres < 7; ++ modres) {
				for(LL has7 = 0; has7 < 2; ++ has7) {
					if(_f[i][sum][modres][has7] != f[i][sum][modres][has7]) {
						cout << "error at f!" << endl;
						return;
					}
					if(_sum1[i][sum][modres][has7] != sum1[i][sum][modres][has7]) {
						cout << "error at s1!" << endl;
						cout << i <<" " << sum <<" " << modres << " " << has7 << endl;
						cout <<_sum1[i][sum][modres][has7] <<"  "<<sum1[i][sum][modres][has7]<<endl;
						return;
					}
					if(_sum2[i][sum][modres][has7] != sum2[i][sum][modres][has7]) {
						cout << "error at s2!" << endl;
						cout <<_sum2[i][sum][modres][has7] <<"  "<<sum2[i][sum][modres][has7]<<endl;
						return;
					}
				}
			}
		}
	}
	cout <<"ok"<<endl;
}

LL ten7[100];

void pre(){
	f[0][0][0][0] = 1;
	ten[0] = 1; ten7[0]=1;
	for(LL i = 1; i <= 99; ++ i) ten[i]=ten[i-1] * 10%P;
	for(LL i = 1; i <= 99; ++ i) ten7[i]=ten7[i-1] * 10%7;
	
	for(LL i = 1; i <= 19; ++ i) {
		for(LL j = 0; j < 10; ++ j) {
			LL sumlim = (i-1) * 9;
			
			for(LL sum = 0; sum <= sumlim; ++ sum) {
				for(LL modres = 0; modres < 7; ++ modres) {
					for(LL has7 = 0; has7 < 2; ++ has7) {
						LL nbit = i, nsum = sum + j, nmod = (modres+ten7[i-1]*j%7)%7,nhas
							= has7 | (j == 7);
						if( nsum <= sumlim + 9) {
							update(f[nbit][nsum][nmod][nhas],
								f[i-1][sum][modres][has7]);
						}
					}
				}
			}
		}
	}
	
	// sum1
	for(LL i = 1; i <= 19; ++ i) {
		for(LL j = 0; j < 10; ++ j) {
			LL sumlim = (i-1) * 9;

			for(LL sum = 0; sum <= sumlim; ++ sum) {
				for(LL modres = 0; modres < 7; ++ modres) {
					for(LL has7 = 0; has7 < 2; ++ has7) {
						LL nbit = i, nsum = sum + j, nmod = (modres+ten7[i-1]*j%7)%7,nhas
							= has7 | (j == 7);
						if( nsum <= sumlim + 9) {
							update(sum1[nbit][nsum][nmod][nhas],
								MOD( sum1[i-1][sum][modres][has7]+((ten[i-1]*j)%P)*f[i-1][sum][modres][has7]%P ) );
						}
					}
				}
			}
		}
	}
	
	// sum2
	for(LL i = 1; i <= 19; ++ i) {
		for(LL j = 0; j < 10; ++ j) {
			LL sumlim = (i-1) * 9;

			for(LL sum = 0; sum <= sumlim; ++ sum) {
				for(LL modres = 0; modres < 7; ++ modres) {
					for(LL has7 = 0; has7 < 2; ++ has7) {
						LL nbit = i, nsum = sum + j, nmod = (modres+ten7[i-1]*j%7)%7,nhas
							= has7 | (j == 7);
						if( nsum <= sumlim + 9) {
							LL tmp = 0, cnt = f[i-1][sum][modres][has7];
							tmp = MOD(tmp + sum2[i-1][sum][modres][has7]);
							tmp = MOD(tmp + (ten[2*i-2]*j*j)%P*cnt%P);
							tmp = MOD(tmp + (ten[i-1]*j*2)%P*sum1[i-1][sum][modres][has7]%P);
							update(sum2[nbit][nsum][nmod][nhas],
								tmp );
						}
					}
				}
			}
		}
	}
}

bool ok(LL x) {
	LL dsum = 0;
	if(x % 7 == 0) return true;
	while(x) {
		LL d = x % 10;
		if(d == 7) return true;
		dsum += d; x/=10;
	}
	return dsum %7 == 0;
}

LL mul(LL a, LL b, LL c) {
	a %= c;
	b %= c;
	return a*b % c;
}

LL gao(LL x) {
	if(x <= 0) return 0;
	LL ans = ok(x)?0:mul(x,x,P);
	//cout << x <<" ans = " << ans << endl;
	LL has7 = 0;
	LL sum = 0;
	LL modres = 0;
	LL pre = 0;
	
	LL d[22],dlen=0;
	while(x) d[dlen++]=x%10,x/=10;
	
	for(LL i = dlen-1,j=0;i>=0;--i,++j) {
		for(LL dg = 0; dg < d[i]; ++ dg) {
			for(LL dsum = 0; dsum <= i*9; ++ dsum) {
				for(LL md = 0; md < 7; ++ md) {
					for(LL msk = 0; msk < 2; ++ msk) {
						
						LL mdsum = dsum + dg + sum;
						
						LL tmp = (modres*10 + dg)%7;
						
						LL mmd = (ten7[i]*tmp+md)%7;
						
						LL mmsk = has7 | msk | (dg == 7);
						
						if( mmsk == 1 || mdsum % 7 ==0 || mmd == 0) continue;

						LL dd = (pre * 10 + dg) % P;
						LL cct = f[i][dsum][md][msk];
						
						update(ans, sum2[i][dsum][md][msk]);
						update(ans, MOD(((dd*2%P)*ten[i])%P*sum1[i][dsum][md][msk]%P));
						update(ans, ((((dd*dd%P)*ten[i])%P*ten[i])%P*cct)%P);
					}
				}
			}
		}
		
		if(d[i] == 7) has7 = 1;
		sum += d[i];
		modres = (modres*10+d[i])%7;
		pre = pre * 10 + d[i];
	}
	return ans;
}

int main(){
	pre();
	LL T;
	cin >> T;
	while(T --) {
		LL L, R; cin >> L >> R;
		cout << MOD(  gao(R)-gao(L-1) ) <<endl;
	/*
		LL ans = 0;
		for(LL x = L; x <= R; ++ x) if(!ok(x))
			update(ans, mul(x,x,P));
			cout << ans << endl;*/
	}
	return 0;
}


2.SGU 258

problem

一个2*n位数,前n位数各位数和与后n位数各位数和相等,是lucky数。一个2*n位数,改变一个数字后,依然是2*n位(改前改后都没有前导零),并且是lucky数,则改之前的数称之为近似lucky数。求[l, r] 区间内,有多少近似lucky数。

think

dp[枚举到那一位][这个数是2*n位数][sum(前n位各位数和-后n位数各位数和)][more(最多增加)][less(最多减少)]
sum可能<0 所以给他都+45
more = max(9-前n位某个数, 后n位某个数)
less = max(首位-1, 前n位且非首位的某个数, 9-后n位某个数) //因为首位不能变成0 所以首位-1
答案是sum!=45(是0 的话就不用变了) && sum+more >=45 && sum-less<=45 

code

const int M = 45;
int f[10][10][91][10][10];
int b[11];

int dfs(int pos, int N, int sum, int more, int less, int e){
    if(pos==0){
        return sum!=M && sum+more>=M && sum-less<=M;
    }
    if(!e && ~f[pos][N][sum][more][less]) return f[pos][N][sum][more][less];
    int ans = 0;
    int u = e?b[pos]:9;
    int d = pos==N?1:0;
    for(; d<=u; d++){
        int ss = pos>N/2 ? sum+d : sum-d;
        int mm = pos>N/2 ? max(more, 9-d) : max(more, d);
        int ll = pos>N/2 ? max(less, pos==N?d-1:d) : max(less, 9-d);
        ans += dfs(pos-1, N, ss, mm, ll, e&&d==u);
    }
    return e ? ans : f[pos][N][sum][more][less] = ans;
}

int s(int n){
    if(n==-1) return 0;
    int p = 0;
    while(n){
        b[++p] = n%10;
        n/=10;
    }
    int ans = 0;
    for(int i=2; i<=p; i+=2){
        ans += dfs(i, i, 0+M, 0, 0, i==p);
    }
    return ans;
}

int main(){
    memset(f, -1, sizeof(f));
    int a, b;
    while(~scanf("%d%d", &a, &b)){
        printf("%d\n", s(b)-s(a-1));
    }
    return 0;
}

3.URAL 1057

problem

[l, r] 区间内,有多少个数分解成K个不同B的次方。

think

平时写数位DP,习惯把数位按十进制分解,这道题,分解成B进制,就可以了。然后看有多少个数里面有K个1 其他都是0.

code

int K, B;
int f[50][50];
int bit[50];

int dfs(int pos, int num, bool e){
    if(pos==0) return num==K;
    if(!e && ~f[pos][num]) return f[pos][num];
    int ans = 0;
    int u = e?bit[pos]:B-1;
    for(int d=0; d<=1 && d<=u; d++){
        ans += dfs(pos-1, num+d, e&&d==u);
    }
    return e?ans:f[pos][num]=ans;
}

int s(int n){
    int p = 0;
    while(n){
        bit[++p] = n%B;
        n/=B;
    }
    return dfs(p, 0, true);
}

int main(){
    int n, m;
    while(~scanf("%d%d%d%d", &n, &m, &K, &B)){
        memset(f, -1, sizeof(f));
        printf("%d\n", s(m)-s(n-1));
    }
    return 0;
}




Step3:撸神题攒经验

Published 10/30/2015 8th Edition 816 pages Book 978-1-5093-0104-1 eBook 978-1-5093-0108-9 Your hands-on guide to Microsoft Visual C# fundamentals with Visual Studio 2015 Expand your expertise--and teach yourself the fundamentals of programming with the latest version of Visual C# with Visual Studio 2015. If you are an experienced software developer, you’ll get all the guidance, exercises, and code you need to start building responsive, scalable Windows 10 and Universal Windows Platform applications with Visual C#. Discover how to: Quickly start creating Visual C# code and projects with Visual Studio 2015 Work with variables, operators, expressions, and methods Control program flow with decision and iteration statements Build more robust apps with error, exception, and resource management Master the essentials of Visual C# object-oriented programming Use enumerations, structures, generics, collections, indexers, and other advanced features Create in-memory data queries with LINQ query expressions Improve application throughput and response time with asynchronous methods Decouple application logic and event handling Streamline development with new app templates Implement the Model-View-ViewModel (MVVM) pattern Build Universal Windows Platform apps that smoothly adapt to PCs, tablets, and Windows phones Integrate Microsoft Azure cloud databases and RESTful web services About You For software developers who are new to Visual C# or who are upgrading from older versions Readers should have experience with at least one programming language No prior Microsoft .NET or Visual Studio development experience required
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值