【DP学习总结】数位DP

文章目录

求【L,R】区间满足某种性质数的个数

模板

int dfs(int pos, int pre, int lead, int limit){
    if(!pos) { 边界 }
    if(!limit && !lead && dp[pos][pre] != -1) return dp[pos][pre];
    int res = 0;
    int mx = limit ? a[pos] : 9;
    for(int i = 0; i < mx; ++i)
    {
        if( 不符合的 ) continue;
        res += dfs(pos - 1, i, i == 0 && lead, limit && i == mx);
    }
    return limit ? res : lead ? res : dp[pos][pre] = res;
}

int cal(int x)
{
    memset(dp, -1, sizeof dp);
    len = 0;
    while (x) a[++len] = x % 10, x /= 10;
    return dfs(len, 0, 1 ,1 ); // 枚举第几个数, 前面一个数是多少,有没有前导零,有没有限制
}
int main()
{
    cin >> L >> R;
    cout << cal(R) - cal(L - 1) << endl;
    return 0;
}

不要62

题意:找到区间 [ L , R ] [L, R] [L,R]内不能出现4或62的次数

Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 15;
int l, r, dp[N][N], len, a[N];
//a[i]表示第i位的数,dp[i][j]表示第i位且前位是pre的满足数的个数
int dfs(int pos, int pre, bool limit)
{
	if(!pos) return 1; // 枚举位数到头了返回1
	if( !limit && dp[pos][pre] != -1) return dp[pos][pre];
	// 记忆化搜索,没有限制并且这个被搜过了就直接返回即可
    int res = 0;
	int mx = limit ? a[pos] : 9; // 判断是否受限
	for(int i = 0; i <= mx; ++i)
	{
       	// 不满足题意得
		if(i == 4 || ( i == 2 && pre == 6)) continue;
		res += dfs(pos - 1, i, limit && i == mx);
		// 枚举下一位,且前一位是i,如果当前有限制且到达了最大,那么下一位也将受限
    }
	return limit ? res : dp[pos][pre] = res;
}

int cal( int x )
{
	memset(dp, -1, sizeof dp); // 初始化
	len  = 0; // 获取该数的每一位
	while ( x ) a[ ++ len] = x % 10 , x /= 10;
	return dfs(len, 0, 1);
}

int main()
{
	while(cin >> l >> r, l && r)
		cout << cal(r) - cal(l - 1) << endl;
	return 0;
}

windy数

H a r d \color{red}{Hard} Hard

题意:求区间 [ L , R ] [L, R] [L,R]之间相邻数字只差不小于 2 2 2的个数

  • sol:初始时, p r e pre pre要设置为一个 ≤ − 2 \le -2 ≤−2的数,为的是找到目标数的首位

Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 15;
int l, r, dp[N][N], len, a[N];
int dfs(int pos, int pre, int lead, bool limit)
{
	if(!pos) return 1;
	if( !limit && !lead && dp[pos][pre] != -1) return dp[pos][pre];
	int res = 0;
	int mx = limit ? a[pos] : 9;
	for(int i = 0; i <= mx; ++i)
	{
		if(abs(pre - i) < 2) continue;
		if(lead && !i) { // 继续往下找到符合数字的第一位
			res += dfs(pos - 1, -2, 1, limit && i == mx);
		}
		else {
			res += dfs(pos - 1, i, 0, limit && i == mx);
		}
	}
	return limit ? res : dp[pos][pre] = res;
}
int cal( int x )
{
	memset(dp, -1, sizeof dp);
	len  = 0;
	while ( x ) a[ ++ len] = x % 10 , x /= 10;
	return dfs(len, -2, 1, 1);
}
int main()
{
	cin >> l >> r;
		cout << cal(r) - cal(l - 1) << endl;
	return 0;
}

花神的数论题

H a r d \color{red}{Hard} Hard
题意: s u m i sum_i sumi​表示 i i i的二进制中1的个数,求 ∏ i = 1 n s u m i \prod_{i=1}^{n}sum_i ∏i=1n​sumi​

Sol:我们定义 G k G_k Gk​为 ∑ i = 1 n ( s u m i = = k ) \sum_{i=1}^n (sum_i == k) ∑i=1n​(sumi​==k),易知答案就为: ∏ i = 1 50 i G i \prod_{i=1}^{50} i^{G_i} ∏i=150​iGi​,利用数位dp求出 1 ∼ n 1 \sim n 1∼n的 G i G_i Gi​,然后快速幂计算

Code:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod =10000007;
LL x,len, dp[64][64], a[64], G[64], P;
LL qpow( LL a, LL b)
{
    LL res = 1;
    while(b) {
        if(b & 1) res = res * a % mod;
        b >>= 1;a = a * a % mod;}
    return res;
}
LL dfs( int pos, int sum, bool limit)
{
	if(!pos) {
		return sum == P;
	}
	if( !limit &&  dp[pos][sum] != -1) return dp[pos][sum];
	LL res = 0;
	int mx = limit ? a[pos] : 1;
	for(int i = 0; i <= mx; ++i)
	{
		if(sum > P) continue;
		res += dfs(pos - 1, sum + (i != 0), limit && i == mx);
	}
	return limit ? res : dp[pos][sum] = res;
}
LL cal( LL x )
{
	len = 0;
	while (x) a[ ++len] = x % 2, x /= 2;
	for( P = 1; P < 50; ++P)
	{
		memset(dp, -1, sizeof dp);
		G[P] = dfs(len, 0, 1); 
	}
	LL ans = 1;
	for(int i = 1; i < 50; ++i)
		ans = ans * qpow(i, G[i])% mod;
	return ans % mod;	
}
int main()
{
	cin >> x;
	cout << cal(x) << endl;
	return 0;
}

Beautiful numbers

题目链接:Beautiful numbers
H a r d \color{red}{Hard} Hard
题意:
T组访问,求出 [ L , R ] [L,R] [L,R]之间的漂亮数.
漂亮数:能够被每个非零位整除的数

  • Sol:
    求区间内特殊性质数的个数,显然要数位DP.
    首先,一个性质:一个数能被他的每个非零位整除,那么这个数也能被各数位的 l c m lcm lcm整除,又因为 1 ∼ 9 1 \sim 9 1∼9的 l c m lcm lcm为 2520 2520 2520,那么只要保证这个数能被 2520 2520 2520整除就是漂亮数.
    考虑到数位DP存不下 9 e 18 9e18 9e18这么大的数,考虑取模.记录模数的同时,还要记录 l c m lcm lcm,这样的话DP数组会开到 d p [ 20 ] [ 2522 ] [ 2522 ] dp[20][2522][2522] dp[20][2522][2522],显然会MLE的,对于 l c m lcm lcm来说,他的因子只有 48 48 48个,其余的都是不符合的不用记录, l c m lcm lcm用哈希映射.

Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1e9+7;
LL dp[22][2522][52];
int a[22];
int hah[2522], cnt;
int len;
LL gcd(LL a, LL b)
{
	return b ? gcd(b, a%b) : a;
}

LL lcm(LL a, LL b)
{
	return a / gcd(a, b) * b;
}
// 初始化,将2520的因子选出来
void init()
{
	for(int i = 1; i <= 2520; ++i)
		if(2520 % i == 0) hah[i] = ++cnt;
}
// 位数,当前的模2520的结果,当前的lcm,是否受限
LL dfs(int pos, int presum, int prelcm, bool limit)
{
	if(!pos) {
        // 能被整除即是漂亮数
		return presum % prelcm == 0;
	}
    // 注意哈希映射
    // 记忆化s
	if(!limit && dp[pos][presum][hah[prelcm]] != -1) return dp[pos][presum][hah[prelcm]];
	int mx = limit ? a[pos] : 9;
	LL res = 0;
	for(int i = 0; i <= mx; ++i)
	{
		int nowlcm = prelcm;
		if(i) nowlcm = lcm(prelcm, i);
		res += dfs(pos - 1, (presum * 10 + i) % 2520, nowlcm, i == mx && limit);
	}
	return limit ? res : dp[pos][presum][hah[prelcm]] = res;
}

LL calc(LL x)
{
	len = 0;
	while(x)
		a[++len] = x % 10, x /= 10;
	return dfs(len, 0, 1, 1);
}

void solve()
{
	LL R , L;
	cin >> L >> R;
	cout << calc(R) - calc(L - 1) << endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    memset(dp, -1, sizeof dp);
    int T = 1;
    init();
    cin >> T;
    while( T -- )
        solve();
    return (0-0);
}

求【L,R】区间满足某种性质数的平方和,立方和

恨7不成妻

H a r d \color{red}{Hard} Hard
题目链接:Acwing 1086 / HDU4507
题意:
如果一个整数符合下面三个条件之一,那么我们就说这个整数和 7 有关:

  • 整数中某一位是 7;
  • 整数的每一位加起来的和是 7 的整数倍;
  • 这个整数是 7 的整数倍。

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

  • sol:讲不清qwq

code:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 20, mod = 1e9 + 7;

int a[N], len;
int pow_10[N];

struct F{
    int s0, s1, s2;
    void operator += (const F &b){
        s0 = (s0 + b.s0 ) % mod;
        s1 = (s1 + b.s1 ) % mod;
        s2 = (s2 + b.s2 ) % mod;
    }
};

F dp[N][N][N];

F dfs(int pos, int m, int sum, bool limit) {
    if(!pos) {
        if(m && sum) return {1, 0, 0};
        else return {0, 0, 0};
    }
    if(!limit && ~dp[pos][sum][m].s0) return dp[pos][sum][m];
    int mx = limit ? a[pos] : 9;
    F res = {0, 0, 0};
    
    for(int i = 0; i <= mx; ++ i) {
        if(i == 7) continue;
        
        F t = dfs(pos - 1, (m * 10 + i) % 7, (sum + i) % 7, limit && i == mx);
        int k = i * pow_10[pos - 1] % mod;
        
        t.s2 = (t.s2 + 2 * k % mod * t.s1 % mod) % mod;
        t.s2 = (t.s2 + k * k % mod * t.s0 % mod) % mod;
        t.s1 = (t.s1 + k * t.s0 % mod) % mod;
        res += t;
    }
    return limit ? res : dp[pos][sum][m] = res;
}

int calc(int x) {
    len = 0;
    while(x) a[++ len] = x % 10, x /= 10;
    return dfs(len, 0, 0, 1).s2;
}

signed main()
{
    memset(dp, -1, sizeof dp);
    pow_10[0] = 1;
    for(int i = 1; i < 20; ++ i)
        pow_10[i] = (pow_10[i - 1]  * 10) % mod;
    int t, l, r;
    cin >> t;
    while(t -- ) {
        cin >> l >> r;
        cout << ( calc(r) % mod - calc(l - 1) % mod + mod) % mod << "\n";
    }
}

不要666升级版

H a r d \color{red}{Hard} Hard
子问题:不要666

  • Sol:讲不清qwq

Code:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 20, mod = 1e9 + 7;

int a[N], len;
int pow_10[N];

struct F{
    int s0, s1, s2, s3;
    void operator += (const F &b){
        s0 = (s0 + b.s0 ) % mod;
        s1 = (s1 + b.s1 ) % mod;
        s2 = (s2 + b.s2 ) % mod;
        s3 = (s3 + b.s3 ) % mod;
    }
};

F dp[N][N][N];

F dfs(int pos, int m, int sum, bool limit) {
    if(!pos) {
        if(m && sum) return {1, 0, 0};
        else return {0, 0, 0};
    }
    if(!limit && ~dp[pos][sum][m].s0) return dp[pos][sum][m];
    int mx = limit ? a[pos] : 9;
    F res = {0, 0, 0};
    
    for(int i = 0; i <= mx; ++ i) {
        if(i == 6) continue;
        
        F t = dfs(pos - 1, (m * 10 + i) % 6, (sum + i) % 6, limit && i == mx);
        int k = i * pow_10[pos - 1] % mod;
        
        
        t.s3 = (t.s3 + k % mod * k % mod * k % mod * t.s0 % mod) % mod;
        t.s3 = (t.s3 + 3 * k % mod * t.s2 % mod + 3 * k % mod * k % mod * t.s1 % mod) % mod;
        t.s2 = (t.s2 + 2 * k % mod * t.s1 % mod) % mod;
        t.s2 = (t.s2 + k * k % mod * t.s0 % mod) % mod;
        t.s1 = (t.s1 + k * t.s0 % mod) % mod;
        res += t;
    }
    return limit ? res : dp[pos][sum][m] = res;
}

int calc(int x) {
    len = 0;
    while(x) a[++ len] = x % 10, x /= 10;
    return dfs(len, 0, 0, 1).s3;
}

signed main()
{
    memset(dp, -1, sizeof dp);
    pow_10[0] = 1;
    for(int i = 1; i < 20; ++ i)
        pow_10[i] = (pow_10[i - 1]  * 10) % mod;
    int t, l, r;
    // cin >> t;
    while(cin >> l >> r) {
        cout << ( calc(r) % mod - calc(l - 1) % mod + mod) % mod << "\n";
    }
}
上一篇:第十二课:人人站模板开发(links 标签获取友情链接列表)


下一篇:【牛客IOI周赛26-普及组 D-最短路 】题解