[置顶] 【数位dp】 Step by Step

看到诸位神牛的代码和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:撸神题攒经验

你可能感兴趣的:([置顶] 【数位dp】 Step by Step)