DP学习笔记

文章目录

  • 记忆化搜索
  • 背包
    • **一.01背包 E a s y \color{green}{Easy} Easy**
    • 二.完全背包 E a s y \color{green}{Easy} Easy
    • 三.多重背包 M i d \color{orange}{Mid} Mid
    • 四.分组背包 E a s y \color{green}{Easy} Easy
  • 线性DP
    • [数字三角形 E a s y \color{green}{Easy} Easy](https://www.acwing.com/problem/content/900/)
    • [最长上升子序列LIS M i d \color{orange}{Mid} Mid](https://www.acwing.com/problem/content/898/)
    • 最长公共子序列LCS E a s y \color{green}{Easy} Easy
      • [字母 E a s y \color{green}{Easy} Easy](https://www.acwing.com/problem/content/899/)
      • [数字 M I D \color{orange}{MID} MID](https://www.luogu.com.cn/problem/P1439)
    • [编辑距离 M i d \color{orange}{Mid} Mid](https://www.luogu.com.cn/problem/P2758)
  • 区间DP
    • [石子合并 E a s y \color{green}{Easy} Easy](https://www.acwing.com/problem/content/description/284/)
    • [最长回文子序列 m i d \color{orange}{mid} mid](https://leetcode-cn.com/problems/longest-palindromic-subsequence/)
  • 树形DP
    • [没有上司的晚会 E a s y \color{green}{Easy} Easy](https://www.acwing.com/problem/content/287/)
  • 数位DP
    • 模板:
    • 相关函数
      • cal函数:
      • 基本参数:
      • dfs函数:
    • 题目
      • [不要62 M i d \color{orange}{Mid} Mid](https://acm.hdu.edu.cn/showproblem.php?pid=2089)
      • [windy数 H a r d \color{red}{Hard} Hard](https://www.luogu.com.cn/problem/P2657)
      • [数字游戏Ⅱ M i d \color{orange}{Mid} Mid](https://loj.ac/p/10164)
      • [数字游戏Ⅰ M i d \color{orange}{Mid} Mid](https://loj.ac/s/1220870)
      • [度的数量 H a r d \color{red}{Hard} Hard](https://acm.timus.ru/problem.aspx?space=1&num=1057)
      • [计数问题 M i d \color{orange}{Mid} Mid](https://www.acwing.com/problem/content/340/)
      • [同类分布 H a r d \color{red}{Hard} Hard](https://www.luogu.com.cn/problem/P4127)
      • [Classy Numbers H a r d \color{red}{Hard} Hard](https://codeforces.com/problemset/problem/1036/C)
      • [花神的数论题 H a r d \color{red}{Hard} Hard](https://www.luogu.com.cn/problem/P4317)
      • [不要666升级版 H a r d \color{red}{Hard} Hard](https://ac.nowcoder.com/acm/problem/213394)

记忆化搜索

滑雪 E a s y \color{green}{Easy} Easy

  • d p [ i ] [ j ] dp[i][j] dp[i][j]表示 ( i , j ) (i, j) (i,j)​位置的最大距离,进行记忆化搜索

Code:

#include
using namespace std;
const int N = 333;
int dp[N][N], mp[N][N];
int n, m;
int dfs(int a, int b)
{
    if(dp[a][b]) return dp[a][b];
    dp[a][b] = 1;
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    for(int i = 0; i < 4; ++i)
    {
        int x = dx[i] + a, y = dy[i] + b;
        if(x >= 1 && x <= n && y >= 1 && y <= m && mp[a][b] > mp[x][y])
        {
            dp[a][b] = max(dp[a][b], dfs(x, y) + 1);
        }
    }
    return dp[a][b];
}
int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= m; ++j)
            cin >> mp[i][j];
    int ans = 0;
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= m; ++j)
            ans = max(ans, dfs(i, j));
    cout << ans << endl;
    return 0;
}

背包

一.01背包 E a s y \color{green}{Easy} Easy

问题描述:

N N N件物品和一个容量为 V V V的背包。第i件物品的费用是 c [ i ] c[i] c[i],价值是 v a l [ i ] val[i] val[i]。求解将哪些物品装入背包可使价值总和最大。

思路:

01背包的特点,每件物品可以选择放或者不放。所有不妨设 F ( i , j ) F(i, j) F(i,j)表示为,前 i i i个物品放进 j j j容量的最大价值,则其状态转移方程为:

f ( i , j ) = m a x ( f ( i − 1 , j ) , f ( i − 1 , j − c [ i ] ) + v a l [ i ] ) f(i, j) = max(f(i-1,j), f(i-1, j-c[i])+val[i]) f(i,j)=max(f(i1,j),f(i1,jc[i])+val[i])

Code:

cin >> n >> m;
    for(int i = 1; i <= n; i++) 
        cin >> v[i] >> w[i];

    for(int i = 1; i <= n; i++) 
        for(int j = 1; j <= m; j++)
        {
            //  当前背包容量装不进第i个物品,则价值等于前i-1个物品
            if(j < v[i]) 
                f[i][j] = f[i - 1][j];
            // 能装,需进行决策是否选择第i个物品
            else    
                f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
        }           
    cout << f[n][m] << endl;

空间优化:

定义** f ( i ) f(i) f(i)**:表示容量为 i i i时的最大价值,则该转移方程为:

f ( i ) = m a x ( f ( i ) , f ( i − c [ i ] ) + v a l [ i ] ) f(i) = max(f(i), f(i-c[i])+val[i]) f(i)=max(f(i),f(ic[i])+val[i]) 注意此时,在枚举容积的时候应该是逆序

Code:

cin >> n >> m;

    for(int i = 1; i <= n; i++) {
        int v, w;
        cin >> v >> w;      // 边输入边处理
        for(int j = m; j >= v; j--)
            f[j] = max(f[j], f[j - v] + w);
    }

    cout << f[m] << endl;

二.完全背包 E a s y \color{green}{Easy} Easy

问题描述:

N N N种物品和一个容量为 N N N的背包,**每种物品都有无限件可用。**第 i i i种物品的费用是 c [ i ] c[i] c[i],价值是 v a l [ i ] val[i] val[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

Solution: f ( i ) f(i) f(i) 表示容量为 i i i的最大价值,由于是无限可用,在枚举容量的是时候顺序枚举,转移方程: f ( i ) = m a x ( f ( i ) , f ( i − c [ i ] ) + v a l [ i ] ) f(i) = max(f(i), f(i-c[i])+val[i]) f(i)=max(f(i),f(ic[i])+val[i])

Code:

cin >> n >> m;

    for(int i = 1; i <= n; i++) {
        int v, w;
        cin >> v >> w;
        for(int j = v; j <= m; j++)
            f[j] = max(f[j], f[j - v] + w);
    }

    cout << f[m] << endl;

三.多重背包 M i d \color{orange}{Mid} Mid

问题描述:

N N N种物品和一个容量为 V V V的背包。第 i i i种物品最多有 n [ i ] n[i] n[i]件可用,每件费用是 c [ i ] c[i] c[i],价值是 v a l [ i ] val[i] val[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

Easy: 0 < N , V ⩽ 100 0 < N, V \leqslant 100 0<N,V100 0 < v , w , s ⩽ 100 0 < v, w, s \leqslant 100 0<v,w,s100

Solution: 直接拆成01背包

Code:

#include
using namespace std;
int n, m;
int f[1010];
int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; ++i)
    {
        int v, w, s; cin >> v >> w >> s;
        for(int j = 1; j <= s; ++j) // 对s次都做一次01背包
        for(int k = m; k >= v; --k)
            f[k] = max(f[k], f[k-v]+w);
    }
    cout << f[m];
    return 0;
}

Mid: 0 < N ⩽ 1000 0 < N \leqslant 1000 0<N1000 0 < V ⩽ 2000 0 < V \leqslant 2000 0<V2000 0 < v , w , s ⩽ 2000 0 < v, w, s \leqslant 2000 0<v,w,s2000

Solution:将每件物品进行二进制拆分。例如:可以选12个物品,则可以拆成1,2,4,5,易知,对于0-12都可以由上述凑出来,如图:

1 1 7 1+2+4
2 1+1 8 4+4
3 1+2 9 5+4
4 2+2 10 5+5
5 1+4 11 5+5+1
6 2+4 12 5+5+2

Code:

#include 
using namespace std;
int n, m;
int f[2020];
struct Good{
    int v,w;
};
int main()
{
    cin >> n >> m;
    vector<Good>g;
    for(int i = 1; i <= n; ++i)
    {
        int v, w, s; cin >> v >> w >> s;
        for(int k = 1; k <= s; k <<= 1)
        {
            s -= k;
            g.push_back({k*v,k*w});
        }
        if(s > 0) g.push_back({s*v,s*w});
    }
    for(auto goods : g)
    for(int j = m; j >= goods.v; --j)
        f[j] = max(f[j], f[j-goods.v] + goods.w);
    cout << f[m];
    return 0;
}

Hard: 0 < N ⩽ 1000 0 < N \leqslant 1000 0<N1000 0 < V ⩽ 20000 0 < V \leqslant 20000 0<V20000 0 < v , w , s ⩽ 20000 0 < v, w, s \leqslant 20000 0<v,w,s20000

Solution:参考题解

四.分组背包 E a s y \color{green}{Easy} Easy

问题描述:

N N N 组物品和一个容量是 V V V 的背包。每组物品有若干个,同一组内的物品最多只能选一个。每件物品的体积是 v i , j v_{i,j} vi,j,价值是 w i , j w_{i,j} wi,j,其中 i i i 是组号, j j j 是组内编号。求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。数据范围均小于100

Solution: 每组进行01背包

Code:

#include
using namespace std;
int n, m;
int f[110], v[110][110], w[110][110], s[110];
int main()
{
    cin >> n >> m;
    for(int i = 1;i <= n; ++i)
    {
        cin >> s[i];
        for(int j=1;j<=s[i]; ++j)
            cin >> v[i][j] >> w[i][j];
    }
    for(int i = 1; i <= n; ++i)// 枚举组,然后01背包
    {
        for(int k = m; k >= 0; --k)//每个h对应每组最优解
        {
            for(int h = 1; h <= s[i]; ++h)
            if(k >= v[i][h]) f[k] = max(f[k], f[k-v[i][h]] + w[i][h]);
        }
    }
    cout << f[m];
    return 0;
}

线性DP

数字三角形 E a s y \color{green}{Easy} Easy

  • 模板题
  • d p [ i ] [ j ] dp[i][j] dp[i][j]表示 ( i , j ) (i, j) (i,j)出的最大值
  • 状态转移方程:从 n − 1 n-1 n1往前推 d p [ i ] [ j ] = d p [ i ] [ j ] + m a x ( d p [ i + 1 ] [ j ] , d p [ i + 1 ] [ j + 1 ] ) dp[i][j] = dp[i][j] + max(dp[i+1][j], dp[i+1][j+1]) dp[i][j]=dp[i][j]+max(dp[i+1][j],dp[i+1][j+1])

Code:

#include
using namespace std;
const int N = 1001;
int dp[N][N], n;
int main()
{
    cin >> n;
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= i; ++j)
            cin >> dp[i][j];
    for(int i = n - 1; i >= 1; --i)
    {
        for(int j = 1; j <= i; ++j)
            dp[i][j] += max(dp[i+1][j], dp[i+1][j+1]);
    }
    cout << dp[1][1];
    return 0;
}

最长上升子序列LIS M i d \color{orange}{Mid} Mid

  • 模板
  • 利用贪心,时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

Code:

#include
using namespace std;
int main()
{
    int n; cin >> n;
    vectora(n), dp(n + 1, 0x3f3f3f3f);
    int mx = dp[0];
    for(int i = 0; i < n; ++i) cin >> a[i];
    for(int i = 0; i < n; ++i)
        // 不严格上升则使用upper_bound
        *lower_bound(dp.begin(), dp.end(), a[i]) = a[i];
    int ans = 0;
    while (dp[ans] != mx) ans ++;
    cout << ans << endl;
    return 0;
}

最长公共子序列LCS E a s y \color{green}{Easy} Easy

字母 E a s y \color{green}{Easy} Easy

  • d p [ i ] [ j ] dp[i][j] dp[i][j]​​​ 表示 a a a​的前 i i i​​个字符串与 b b b的前 j j j​​个字符的最长公共子序列的大小
    d p [ i ] [ j ] ⟮ m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) d p [ i − 1 ] [ j − 1 ] , a [ i ] = = b [ i ] dp[i][j] \lgroup^{dp[i-1][j-1], a[i] == b[i]} _{max(dp[i-1][j], dp[i][j-1])} dp[i][j]max(dp[i1][j],dp[i][j1])dp[i1][j1],a[i]==b[i]​​​​

Code:

#include
using namespace std;
const int N = 1010;
int dp[N][N];
int main()
{
    int n, m; cin >> n >> m;
    string a, b; cin >> a >> b;
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= m; ++j)
        {
            if(a[i - 1] == b[j - 1])
                dp[i][j] = dp[i-1][j-1] + 1;
            else dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
        }
    cout << dp[n][m] << endl;
    return 0;
}

数字 M I D \color{orange}{MID} MID

  • 给出两个排列 P 1 , P 2 P_1,P_2 P1,P2​,求他们的 L C S LCS LCS n ≤ 1 0 5 n \le 10^5 n105
  • S o l : Sol: Sol​按照 P 1 P_1 P1​对 P 2 P_2 P2​离散化,然后对 P 2 P_2 P2​求 L I S LIS LIS即可

Code:

#include
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const int N = 1e5+10;
int dp[N];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
	int n; cin >> n;
	vectora(n), b(n);
	unordered_mapmp;
	for(int i = 0; i < n; ++i) cin >> a[i], mp[a[i]] = i;
	for(int i = 0; i < n; ++i) cin >> b[i];
	memset(dp, 0x3f, sizeof dp);
	int mx = dp[n];
	for(int i = 0; i < n; ++i)
		*upper_bound(dp, dp+n, mp[b[i]]) = mp[b[i]];
	int ans = 0;
	while( dp[ans] != mx) ans ++;
	cout << ans << endl; 	   
    return(0-0);
}

编辑距离 M i d \color{orange}{Mid} Mid

  • d p [ i ] [ j ] dp[i][j] dp[i][j]​​​表示 a a a​​​的前 i i i​​个字符变成 b b b​的前 j j j​个字符的组少操作数​​

    转移方程:
    d p [ i ] [ j ] dp[i][j] dp[i][j]​ =

    ​ a. 删除: d p [ i − 1 ] [ j ] + 1 dp[i-1][j] + 1 dp[i1][j]+1

               2. 添加:$dp[i][j-1] + 1$
                  3. 替换:$dp[i-1][j-1] + 1$
                  4. 不变:$dp[i-1][j-1]$
    
#include
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const int N = 2020;
int dp[N][N]; 
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
	string a, b; cin >> a >> b;
	int n = a.size(), m = b.size();
	for(int i = 1; i <= n; ++i) dp[i][0] = i;
	for(int i = 1; i <= m; ++i) dp[0][i] = i;

	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= m; ++j)
		{
			if(a[i-1] == b[j-1]) dp[i][j] = dp[i-1][j-1];
			else {                                                
				dp[i][j] = min(min(dp[i-1][j] + 1, dp[i][j-1] + 1), dp[i-1][j-1] + 1);
			}
		}
	cout << dp[n][m] << endl;

    return(0-0);
}

区间DP

石子合并 E a s y \color{green}{Easy} Easy

Code:

#include
using namespace std;
const int N = 333, INF = 2e9;
int w[N], s[N], n;
int dp[N][N];

int main()
{
    cin >> n;
    for(int i = 1; i <= n; ++i) cin >> w[i];
    for(int i = 1; i <= n; ++i) s[i] = s[i - 1] + w[i];
    
    for(int len = 2; len <= n; ++len)
    {
        for(int l = 1; len + l - 1 <= n; ++l)
        {
            int r = len + l - 1;
            dp[l][r] = INF;
            for(int k = l; k < r; ++k)
                dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r] + s[r] - s[l - 1]);
        }
    }
    cout << dp[1][n] << endl;
    return 0;
}

最长回文子序列 m i d \color{orange}{mid} mid

  • d p [ i ] [ j ] dp[i][j] dp[i][j]​​​表示 i i i​到 j j j的最长回文长度
  • 转移方程: [ d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , m a x ( d p [ i ] [ j − 1 ] , d p [ i + 1 ] [ j ] ) ) , s [ i ] ! = s [ j ] d p [ i ] [ j ] = d p [ i + 1 ] [ j − 1 ] + 2 , s [ i ] = = s [ j ] ] ] \left [ _{dp[i][j] = max(dp[i][j], max(dp[i][j-1],dp[i+1][j])), s[i] !=s[j]} ^{dp[i][j] = dp[i+1][j-1] + 2, s[i] == s[j]]} \right ] [dp[i][j]=max(dp[i][j],max(dp[i][j1],dp[i+1][j])),s[i]!=s[j]dp[i][j]=dp[i+1][j1]+2,s[i]==s[j]]]

Code:

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int n = s.size();
        vector<vector<int>>dp(n,  vector<int>(n));
        for(int len = 1; len <= n; ++len)
            for(int l = 0; l + len - 1 < n; ++l)
            {
                int r = len + l - 1;
                if(len == 1) dp[l][r] = 1;
                else {
                    if(s[l] == s[r]) dp[l][r] = dp[l+1][r-1] + 2;
                    else {
                        dp[l][r] = max(dp[l][r], max(dp[l+1][r], dp[l][r-1]));
                    }
                }
            }
        int ans = 0;
        for(auto &arr : dp)
            for(auto &x : arr)
                ans = max(ans, x);
        return ans;
    }
};

树形DP

没有上司的晚会 E a s y \color{green}{Easy} Easy

题意:Ural 大学有 N N N 名职员,编号为 1 ∼ N 1 \sim N 1N​。他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。每个人有一个开心值,选出若干人,使得开心值最大,没有职员愿意和直接上司一起

  • d p [ i ] [ 1 ] dp[i][1] dp[i][1]​​表示选择 i i i的最大值, d p [ i ] [ 0 ] dp[i][0] dp[i][0]​表示不选择 i i i​的最大值

Code:

#include
using namespace std;
const int N = 6010;
int e[N], ne[N], h[N], idx;
int happy[N], n, dp[N][2];
bool f[N];
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs(int u)
{
    dp[u][1] = happy[u];
    
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        dfs(j);
        dp[u][0] += max(dp[j][0], dp[j][1]);
        dp[u][1] += dp[j][0]; 
    }
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; ++i) cin >> happy[i];
    
    memset(h, -1, sizeof h);
    
    for(int i = 1; i < n; ++i)
    {
        int a, b; cin >> a >> b;
        f[a] = true;
        add(b, a);
    }
    int root = 1;
    while(f[root]) root++;
    dfs(root);    
    cout << max(dp[root][1], dp[root][0]) << endl;
    return 0;
}

数位DP

数位 D P DP DP​​问题一般是给出一个区间 [ L , R ] [L,R] [L,R]​​,问区间中满足给定条件的数的数量。解决办法就是前缀和求解: ∑ i = 1 R a n s i − ∑ i = 1 L − 1 a n s i \sum_{i=1}^{R}ans_i - \sum_{i=1}^{L-1} ans_i i=1Ransii=1L1ansi​​

模板:

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, , , 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, , 1 ,1 );
}
int main()
{
    cin >> L >> R;
    cout << cal(R) - cal(L - 1) << endl;
    return 0;
}

相关函数

cal函数:

初始化 d p dp dp数组为-1,数长度为0;

基本参数:

l e n len len​​​ 表示这个数的长度, a i a_i ai​​​表示每个数位的具体数字
r e t u r n return return​​​:需要根据题意做出判断
p o s pos pos表示数字的位数
l i m i t limit limit表示相关的限制
p r e pre pre表示前驱
$lead表示前导零是否存在

dfs函数:

r e s res res​记录答案, m x mx mx表示该位的最大值,此外采用了记忆化搜索。

if(!limit && !lead && dp[pos][pre] != -1) return dp[pos][pre];
  • 只有无限制,没有前导零才算是搜完了,不然都是没有搜完的
return limit ? res : dp[pos][pre] = res;
  • 如果最后还有限制,就返回 r e s res res,否则返回 d p [ p o s ] [ p r e ] dp[pos][pre] dp[pos][pre]

题目

不要62 M i d \color{orange}{Mid} Mid

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

Code:

#include
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的个数

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

Code:

#include
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;
}

数字游戏Ⅱ M i d \color{orange}{Mid} Mid

题意:计算 [ L , R ] [L, R] [L,R] 各位数字不下降的个数

Code:

#include
using namespace std;
const int N = 15;
int l, r, dp[N][N], len, a[N];

int dfs(int pos, int pre, bool limit)
{
	if(!pos) return 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 < pre) continue;
		res += dfs( pos - 1, i, 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, 0, 1);
}
int main()
{
	while(cin >> l >> r)
		cout << cal(r) - cal(l - 1) << endl;
	return 0;
}

数字游戏Ⅰ M i d \color{orange}{Mid} Mid

题意:计算 [ L , R ] [L, R] [L,R]​每个数位之和 m o d p mod p modp n n n == 0 的个数

Code:

#include
using namespace std;
const int N = 110;
int l, r, dp[N][N], len, a[N], n;
int dfs(int pos, int sum, bool limit)
{
	if(!pos) return sum % n == 0;
	if( !limit && dp[pos][sum] != -1) return dp[pos][sum];
	int res = 0;
	int mx = limit ? a[pos] : 9;
	for(int i = 0; i <= mx; ++i)
	{
		res += dfs( pos - 1, sum + i, limit && i == mx);
	}
	return limit ? res : dp[pos][sum] = res;
}
int cal( int x )
{
    if(!x) return 1;
	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 >> n)
		cout << cal(r) - cal(l - 1) << endl;
	return 0;
}

度的数量 H a r d \color{red}{Hard} Hard

题意:计算 [ L , R ] [L, R] [L,R]恰好为 K K K B B B​​​的幂次方之和的个数,并且这些书都不相同

  • tag :实质上就是求 B B B​​进制下 1 1 1的个数要等于 K K K​,​

Code:

#include

using namespace std;
const int N = 35;
int l, r, dp[N][N], len, a[N], k , b;
int dfs(int pos, int sum, bool limit)
{
	if(!pos) return sum == k;
	if( !limit && dp[pos][sum] != -1) return dp[pos][sum];
	int res = 0;
	int mx = limit ? a[pos] : b - 1;
	for(int i = 0; i <= mx; ++i)
	{
        //如果前面系数不为1或者以及到达k个就跳过
		if( (i==1 && sum == k)|| i > 1) continue;
		res += dfs(pos - 1, sum + ( i == 1), limit && i == mx);
	}
	return limit ? res : dp[pos][sum] = res;
}
int cal( int x )
{
	memset(dp, -1, sizeof dp);
	len  = 0;
	while ( x ) a[ ++ len] = x % b , x /= b;
	return dfs(len, 0, 1);
}
int main()
{
	while(cin >> l >> r >> k >> b)
		cout << cal(r) - cal(l - 1) << endl;
	return 0;
}

计数问题 M i d \color{orange}{Mid} Mid

题意:计算 [ L , R ] [L, R] [L,R]​​​之间出现$0 \sim 9 $​​的个数

Code:

#include
using namespace std;
const int N = 35;
int l, r, dp[N][N], len, a[N];
int dfs(int pos, int sum, int num, bool lead, bool limit)
{
	if(!pos) {
		if(lead && !num) return 1;
		return sum;
	}
	if( !limit && !lead && dp[pos][sum] != -1) return dp[pos][sum];
	int res = 0;
	int mx = limit ? a[pos] : 9;
	for(int i = 0; i <= mx; ++i)
	{
		int t;
		if( i == num) {
			if(!num) t = sum + (lead == 0);
			else t = sum + 1;
		}
		else t = sum;
		res += dfs(pos - 1, t, num, lead && i == 0, limit && i == mx);
	
	}
	return limit ? res : (lead ? res : dp[pos][sum] = res);
}

int cal( int x, int num)
{
	memset(dp, -1, sizeof dp);
	len  = 0;
	while ( x ) a[ ++ len] = x % 10 , x /= 10;
	return dfs(len, 0,num, 1, 1);
}

int main()
{
	while(cin >> l >> r, l && r) {
		if(l > r) swap(l ,r);
		for(int i = 0; i < 10; ++i)
			cout << cal(r, i) - cal(l-1, i) << " \n"[i == 9];
	}
	return 0;
}

同类分布 H a r d \color{red}{Hard} Hard

题意:求出 [ L , R ] [L, R] [L,R]之间各位数字之和能整除原书的个数

Sol:套用数位DP模板, m o d mod mod​​表示各位数和, s u m sum sum​表示原数,发现这样的话 s u m sum sum原数将达到 1 0 18 10^{18} 1018,记忆化数组存不下,故我们考虑枚举各位数和,即模数

Code:

#include 
using namespace std;
typedef long long LL;
LL l, r,len, dp[20][200][200], a[20], Mod;
LL dfs( int pos, int mod, LL sum, bool limit)
{
	if(!pos) {
		return (sum == 0 && mod == Mod);
	}
	if( !limit && dp[pos][mod][sum] != -1) return dp[pos][mod][sum];
	LL res = 0;
	int mx = limit ? a[pos] : 9;
	for(int i = 0; i <= mx; ++i)
		res += dfs( pos - 1, (mod + i),( sum * 10 + i) % Mod, limit && i == mx);
	return limit ? res : dp[pos][mod][sum] = res;
}

LL cal( LL x )
{
	len = 0;
	while (x) a[ ++len] = x % 10, x /= 10;
	LL ans = 0;//枚举模数,即各位数和
	for( Mod = 1; Mod <= 9 * len; ++Mod)
	{
		memset(dp, -1, sizeof dp);
		ans += dfs(len, 0, 0, 1);
	}
	return ans;
}

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

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

题意:计算 [ L , R ] [L, R] [L,R]之间各数的非零数不超过三个

Sol:套模板即可

#include 
using namespace std;
typedef long long LL;
LL l, r,len, dp[20][20], a[20];
LL dfs( int pos, int sum, bool lead, bool limit)
{
	if(!pos) {
		return 1;
	}
	if( !limit && !lead && dp[pos][sum] != -1) return dp[pos][sum];
	LL res = 0;
	int mx = limit ? a[pos] : 9;
	for(int i = 0; i <= mx; ++i)
	{
		if(i != 0 && sum >= 3) continue;
		res += dfs(pos - 1, sum + (i != 0), lead && i == 0, limit && i == mx);
	}
	return limit ? res : (lead ? res : dp[pos][sum] = res);
}
LL cal( LL x )
{
	len = 0;
	while (x) a[ ++len] = x % 10, x /= 10;
	return dfs(len, 0, 1, 1);
}
int main()
{
	int T; cin >> T;
	memset(dp, -1, sizeof dp);
	while ( T -- )
	{
		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=1nsumi​​

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=150iGi,利用数位dp求出 1 ∼ n 1 \sim n 1n G i G_i Gi,然后快速幂计算

Code:

#include 
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;
}

不要666升级版 H a r d \color{red}{Hard} Hard

子问题:不要666

Sol:一个数分为 a + b a+b a+b​​​​,例如: 82342 = 80000 + 2342 82342 = 80000+2342 82342=80000+2342​​​​
( a + b ) 3 = a 3 + b 3 + 3 ∗ a 2 ∗ b + 3 ∗ a ∗ b 2 (a+b)^3 = a^3 + b^3+3*a^2*b+3*a*b^2 (a+b)3=a3+b3+3a2b+3ab2​​​
满足条件的数, c n t cnt cnt​为这些数字的个数: ( a 3 ∗ c n t + ( b 3 + c 3 + ⋅ ⋅ ⋅ ) + 3 ∗ a 2 ∗ ( b + c + ⋅ ⋅ ⋅ ) + 3 ∗ a ∗ ( b 2 + c 2 + ⋅ ⋅ ⋅ ) ) (a^3*cnt + (b^3 + c^3 + ···) + 3 * a^2*(b+c+···) + 3*a*(b^2 + c^2+···)) (a3cnt+(b3+c3+)+3a2(b+c+)+3a(b2+c2+))​​
数位 d p dp dp维护其个数,总和,平方和,立方和

Code:

#include
using namespace std;
typedef long long LL;
const int N = 30, mod = 1e9+7;
LL l, r, len, a[N], ten[N];
struct s
{ 
    LL x, sum, sqr, cub;
} dp[20][10][10];
s dfs(int pos, int mod1, int mod2, bool limit ){
	if(!pos) return (s){mod1&&mod2, 0, 0, 0};
	if( !limit && dp[pos][mod1][mod2].x != -1) return dp[pos][mod1][mod2];
	int mx = limit ? a[pos] : 9;
	s res = {0, 0, 0, 0};
	for(int i = 0; i <= mx ; ++i)
	{
		if( i == 6) continue;
		s t = dfs(pos - 1, (mod1 + i) % 6, (mod2 * 10 + i) % 6, limit && i == mx);
		LL d = (LL)i * ten[pos] % mod;

	    res.x = (res.x + t.x) % mod;
        res.sum = (res.sum + d * t.x % mod  + t.sum) % mod;
        res.sqr = (res.sqr + t.x * d % mod * d % mod + 2LL * d % mod * t.sum % mod + t.sqr) % mod;
        res.cub = (res.cub + t.x * d % mod * d % mod * d % mod + 3LL * d % mod * d % mod * t.sum % mod + 3LL * d % mod * t.sqr % mod + t.cub) % mod;
	}
	return limit ? res : dp[pos][mod1][mod2] = res;
}

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

int main()
{
	memset(dp, -1, sizeof dp);
	ten[1] = 1;
	for(int i = 2; i <= 20; ++i) ten[i] = ten[i-1]*10;	
	while (cin >> l >> r )
		cout << (cal(r) - cal(l - 1)+mod) % mod << endl;
	return 0;	
}

你可能感兴趣的:(学习总结,动态规划,深度优先,动态规划,算法)