zoukankan      html  css  js  c++  java
  • 数位dp小练

    最近刷题的同时还得填填坑,说来你们也不信,我还不会数位dp。


    照例推几篇博客:
    数位DP讲解
    数位dp 的简单入门
    这两篇博客讲的都很好,不过代码推荐记搜的形式,不仅易于理解,还短。


    数位dp的式子一般是这样的:dp[i][][]表示到第(i)位,而后面几维就因题而异了。
    不过通用的思想就是利用前缀相减求出区间信息。
    算了上题吧。


    [SCOI2009]windy数
    这都说是数位dp入门题。
    根据这题,受到影响的数只有相邻两个,因此dp[i][j]表示到第(i)位(从高往低)上一位的数(j)的答案。
    接下来的关键在于怎么判断到达上界的情况。那么我们在搜索的时候加一个bool变量_Max表示是否到达上界,如果是,那么这一位的最大值就是该位上的数,否则就是9。
    然后关于前导零,我又开了一个bool变量zero判断他之前有没有过非0的数。
    接下来就是记搜的内容了。先写一个爆搜,然后把答案存在dp里,基本就是记搜了。

    #include<cstdio>
    #include<iostream>
    #include<cmath>
    #include<algorithm>
    #include<cstring>
    #include<cstdlib>
    #include<cctype>
    #include<vector>
    #include<stack>
    #include<queue>
    using namespace std;
    #define enter puts("") 
    #define space putchar(' ')
    #define Mem(a, x) memset(a, x, sizeof(a))
    #define In inline
    typedef long long ll;
    typedef double db;
    const int INF = 0x3f3f3f3f;
    const db eps = 1e-8;
    const int maxn = 12;
    inline ll read()
    {
    	ll ans = 0;
    	char ch = getchar(), last = ' ';
    	while(!isdigit(ch)) last = ch, ch = getchar();
    	while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
    	if(last == '-') ans = -ans;
    	return ans;
    }
    inline void write(ll x)
    {
    	if(x < 0) x = -x, putchar('-');
    	if(x >= 10) write(x / 10);
    	putchar(x % 10 + '0');
    }
    
    int dp[maxn][maxn], num[maxn], cnt = 0;
    
    In int dfs(int len, int las, bool _Max, bool zero)
    {
        if(!len) return 1;  //表示出现了一个符合条件的数,就返回1
    	if(!_Max && !zero && dp[len][las] != -1) return dp[len][las];
    	//只有普通情况才可以返回记搜的答案.
    	int pos, ret = 0, Max = _Max ? num[len] : 9;
    	for(int i = 0; i <= Max; ++i)
    	{
    		if(abs(i - las) < 2) continue;
    		pos = (zero && !i) ? -INF : i;  //如果前面都是0且这一位还填0,就标记为INF
    		ret += dfs(len - 1, pos, _Max && i == Max, pos == -INF);
    	}
    	if(!_Max && !zero) dp[len][las] = ret;
    	return ret;
    }
    
    In int solve(int n)
    {
    	Mem(dp, -1); cnt = 0;
    	while(n) num[++cnt] = n % 10, n /= 10;  //处理每一位
    	return dfs(cnt, -INF, 1, 1);
    }
    
    int main()
    {
    	int a = read(), b = read();
    	write(solve(b) - solve(a - 1)), enter;
    	return 0;
    }
    

    **[luogu P3413 SAC#1 - 萌数](https://www.luogu.org/problemnew/show/P3413)** 这题反正我是不会,看了题解也没懂。最后在dukelv的帮助下搞明白了。 首先题解中有很多篇用了正难则反的思想,但其实没必要。 考虑最小的回文串,无非两种:aa, aba。 所以我们只用找出这两种回文串即可。因为大的回文串比如acbbca,在搜bb的时候,下几个状态就包括这个回文串了。 那么就要讨论奇偶,所以爆搜的时候开两个变量pre,per分别记录上一位和上上一位,然后判断和当前位是否构成回文串。 别忘了还可能会出现构不成萌数的情况,所以dp方程除了dp[i][j]表示到了第$i$位,这一位填$i$,还应再加一位[0/1]表示当前是否存在萌数,这样能剪枝不少。 ```c++ #include #include #include #include #include #include #include #include #include #include using namespace std; #define enter puts("") #define space putchar(' ') #define Mem(a, x) memset(a, x, sizeof(a)) #define In inline typedef long long ll; typedef double db; const int INF = 0x3f3f3f3f; const db eps = 1e-8; const int maxn = 1e3 + 5; const ll mod = 1e9 + 7; inline ll read() { ll ans = 0; char ch = getchar(), last = ' '; while(!isdigit(ch)) last = ch, ch = getchar(); while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar(); if(last == '-') ans = -ans; return ans; } inline void write(ll x) { if(x < 0) x = -x, putchar('-'); if(x >= 10) write(x / 10); putchar(x % 10 + '0'); }

    char a[maxn], b[maxn];
    int num[maxn], len = 0;

    ll dp[maxn][10][2];
    In ll dfs(int pos, int pre, int per, bool _t, bool _k, bool _Max)
    {
    if(!pos) return _t; //_t表示当前是否存在萌数
    if(!_Max && dp[pos][pre][_t] != -1) return dp[pos][pre][_t];
    int Max = _Max ? num[pos] : 9;
    ll ret = 0;
    for(int i = 0; i <= Max; ++i)
    ret += dfs(pos - 1, i, _k ? pre : -1, _t || (i == pre && _k) || (i == per && _k), _k || i, _Max && i == Max), ret %= mod;
    if(!_Max && _k && per != -1) dp[pos][pre][_t] = ret % mod;
    return ret;

    }
    In ll solve(char* s)
    {
    len = strlen(s + 1);
    for(int i = 1; i <= len; ++i) num[i] = s[i] - '0';
    reverse(num + 1, num + len + 1);
    Mem(dp, -1);
    return dfs(len, -1, -1, 0, 0, 1);
    }

    int main()
    {
    scanf("%s%s", a + 1, b + 1);
    int tp = strlen(a + 1);
    reverse(a + 1, a + tp + 1);
    int pos = 1; //别忘了是高精减1……
    --a[pos];
    while(pos < tp && a[pos] < '0') a[pos++] = '9', --a[pos];
    while(tp > 1 && a[tp] <= '0') a[tp--] = '';
    reverse(a + 1, a + tp + 1);
    write((solve(b) - solve(a) + mod) % mod), enter;
    return 0;
    }

  • 相关阅读:
    POJ 3356 水LCS
    POJ 2250 (LCS,经典输出LCS序列 dfs)
    POJ 1080( LCS变形)
    整数划分问题之最大乘积
    进程调度之FCFS算法(先来先运行算法)
    c模拟银行家资源分配算法
    c模拟内存分配算法(首次适应算法,最佳适应算法,最坏适应算法)
    HDU 2602 Bone Collector(经典01背包问题)
    NYOJ 44 字串和 (最大字串和 线性dp)
    匈牙利游戏(codevs 1269)
  • 原文地址:https://www.cnblogs.com/mrclr/p/10367978.html
Copyright © 2011-2022 走看看