数位dp小记

参考:

http://www.cnblogs.com/jffifa/archive/2012/08/17/2644847.html

kuangbin :http://www.cnblogs.com/kuangbin/category/476047.html

http://blog.csdn.net/cmonkey_cfj/article/details/7798809

http://blog.csdn.net/liuqiyao_01/article/details/9109419

数位dp有递推和记忆化搜索的方法,比较来说记忆化搜索方法更好。通过博客一的一个好的模板,数位dp就几乎变成一个线性dp问题了。

下为博客一内容:

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

偷看了下7k+大牛的数位统计dp写法,通常的数位dp可以写成如下形式:

复制代码
int dfs(int i, int s, bool e) {
    if (i==-1) return s==target_s;
    if (!e && ~f[i][s]) return f[i][s];
    int res = 0;
    int u = e?num[i]:9;
    for (int d = first?1:0; d <= u; ++d)
        res += dfs(i-1, new_s(s, d), e&&d==u);
    return e?res:f[i][s]=res;
}
复制代码

其中:

f为记忆化数组;

i为当前处理串的第i位(权重表示法,也即后面剩下i+1位待填数);

s为之前数字的状态(如果要求后面的数满足什么状态,也可以再记一个目标状态t之类,for的时候枚举下t);

e表示之前的数是否是上界的前缀(即后面的数能否任意填)。

for循环枚举数字时,要注意是否能枚举0,以及0对于状态的影响,有的题目前导0和中间的0是等价的,但有的不是,对于后者可以在dfs时再加一个状态变量z,表示前面是否全部是前导0,也可以看是否是首位,然后外面统计时候枚举一下位数。It depends.

于是关键就在怎么设计状态。当然做多了之后状态一眼就可以瞄出来。

注意:

不满足区间减法性质的话(如hdu 4376),不能用solve(r)-solve(l-1),状态设计会更加诡异。

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

正如上面说的要注意:

前导零是否有影响。

是否满足区间减的性质。(如hdu4376,还没做,先记上)


几乎就是模板题:

模板:

UESTC 1307相邻的数差大于等于2

(不允许有前导0,前导0对计算有影响,注意前导0的处理)

int dp[15][10];
int bit[15];
int dfs(int pos, int s, bool limit, bool fzero)
{
     if (pos == -1) return 1;///前导0的影响!!!
    if (!limit && !fzero && ~dp[pos][s]) return dp[pos][s];///条件判断!!!
    int end = limit ? bit[pos] : 9;

    int ans = 0;
    for (int i = 0; i <= end; i++)
    {
        if (!fzero && abs(i - s) < 2) continue;///前导0的影响!!!
        int nows = i;
        ans += dfs(pos - 1, nows, limit && (i == end), fzero && !i);
    }

    return limit || fzero ? ans : dp[pos][s] = ans;///条件判断!!!
}
int calc(int n)
{
    if (n == 0) return 1;
    int len = 0;
    while (n)
    {
        bit[len++] = n % 10;
        n /= 10;
    }
    return dfs(len - 1, 0, 1, 1);
}
int main ()
{
    memset(dp, -1, sizeof(dp));
    int l, r;
    while (cin >> l >> r)
    {
        cout << calc(r) - calc(l - 1) << endl;
    }
    return 0;
}

其它一些题目:

Hdu3555不能出现连续的49

int bit[25];
LL dp[25][3];
/**
0
1
2
*/

/**
pos为当前考虑的位置
s为在pos之前已到达的状态
limit当前考虑的数字是否有限制,即之前已确定的数是否为n的前缀
*/
LL dfs(int pos, int s, bool limit)
{
    if (pos == -1) return s == 2;
    if (!limit && ~dp[pos][s]) return dp[pos][s];
    int end = limit ? bit[pos] : 9;///limit选择
    LL ans = 0;

    for (int i = 0; i <= end; i++)
    {
        int nows;
        if (s == 0)
        {
            if (i == 4) nows = 1;
            else nows = 0;
        }
        else if (s == 1)
        {
            if (i == 9) nows = 2;
            else if (i == 4) nows = 1;
            else nows = 0;
        }
        else if (s == 2) nows = 2;

        ans += dfs(pos - 1, nows, limit && i == end);
    }

    return limit ? ans : dp[pos][s] = ans;///limit选择
}

LL calc(LL n)
{
    ///
    int len = 0;
    while (n)
    {
        bit[len++] = n % 10;
        n /= 10;
    }
    ///
    return dfs(len - 1, 0, 1);
}
int main ()
{
    memset(dp, -1, sizeof(dp));
    int T;
    RI(T);
    LL n;
    while (T--)
    {
        cin >> n;
        cout << calc(n) << endl;
    }
    return 0;
}

hdu2089 不要62 

int dp[10][3];
int bit[10];
int dfs(int pos, int s, int limit, bool first)
{
    if (pos == -1) return s == 2;
    if (!limit && ~dp[pos][s]) return dp[pos][s];
    int end = limit ? bit[pos] : 9;
//    int be = first ? 1 : 0;


    int ans = 0;
    for (int i = 0; i <= end; i++)
    {
        int nows = s;
        if (s == 0)
        {
            if (i == 6) nows = 1;
            else if (i == 4) nows = 2;
        }
        if (s == 1)
        {
            if (i == 2 || i == 4) nows = 2;
            else if (i == 6) nows = 1;
            else nows = 0;
        }
        if (i == 4) nows = 2;

        ans += dfs(pos - 1, nows, limit && (i == end), first && !i);
    }

    return limit ? ans : dp[pos][s] = ans;
}
int calc(int n)
{
    int tmp = n;
    if (n == 0) return 0;
    int len = 0;
    while (n)
    {
        bit[len++] = n % 10;
        n /= 10;
    }
    return tmp - dfs(len - 1, 0, 1, 1);
}
int main ()
{
    memset(dp, -1, sizeof(dp));
    int l, r;
//    for (int i = 1; i <= 100; i++)
//        cout << i << ' ' << calc(i) << endl;
    while (cin >> l >> r)
    {
        if (l + r == 0) break;
//        cout << calc(r) << ' ' << calc(l - 1) << endl;
        cout << calc(r) - calc(l - 1) << endl;
    }

    return 0;
}

UESTC 1307相邻的数差大于等于2

(注意前导0的处理)

int dp[15][10];
int bit[15];
int dfs(int pos, int s, bool limit, bool fzero)
{
     if (pos == -1) return 1;///前导0的影响!!!
    if (!limit && !fzero && ~dp[pos][s]) return dp[pos][s];///条件判断!!!
    int end = limit ? bit[pos] : 9;

    int ans = 0;
    for (int i = 0; i <= end; i++)
    {
        if (!fzero && abs(i - s) < 2) continue;///前导0的影响!!!
        int nows = i;
        ans += dfs(pos - 1, nows, limit && (i == end), fzero && !i);
    }

    return limit || fzero ? ans : dp[pos][s] = ans;///条件判断!!!
}
int calc(int n)
{
    if (n == 0) return 1;
    int len = 0;
    while (n)
    {
        bit[len++] = n % 10;
        n /= 10;
    }
    return dfs(len - 1, 0, 1, 1);
}
int main ()
{
    memset(dp, -1, sizeof(dp));
    int l, r;
    while (cin >> l >> r)
    {
        cout << calc(r) - calc(l - 1) << endl;
    }
    return 0;
}

POJ 3252  Round Number (组合数)!!!

拆成2进制,在记录0和1的个数

求区间[l,r]中,满足传化成2进制后,0的个数>=1的个数的,数字的个数

int dp[40][40][40];
int bit[40];
int dfs(int pos, int num0, int num1, bool limit, bool fzero)
{
     if (pos == -1) return num0 >= num1;///前导0的影响!!!
    if (!limit && !fzero && ~dp[pos][num0][num1]) return dp[pos][num0][num1];///条件判断!!!
    int end = limit ? bit[pos] : 1;

    int ans = 0;
    for (int i = 0; i <= end; i++)
    {
        int new0, new1;
        if (fzero)
        {
            new0 = 0, new1 = 0;
            if (i) new1 = 1;
        }
        else
        {
            new0 = num0, new1 = num1;
            if (i) new1++;
            else new0++;
        }
        ans +=  dfs(pos - 1, new0, new1, limit && (i == end), fzero && !i);
    }
    return limit || fzero ? ans : dp[pos][num0][num1] = ans;///条件判断!!!
}
int calc(int n)
{
    if (n == 0) return 1;
    int len = 0;
    while (n)
    {
        bit[len++] = n % 2;
        n /= 2;
    }
    return dfs(len - 1, 0, 0, 1, 1);
}
int main ()
{
    memset(dp, -1, sizeof(dp));
    int l, r;
    while (cin >> l >> r)
    {
        cout << calc(r) - calc(l - 1) << endl;
    }
    return 0;
}


hdu3886求满足符号串的数字个数!!!

统计满足和指定 升降字符串 匹配的个数

int dp[105][105][10];
int bit[105];
char s[105];
char a[105], b[105];
int ns;

bool ok(int r, int a, int b)
{
    if (s[r] == '/') return a < b;
    else if (s[r] == '-') return a == b;
    else if (s[r] == '\\') return a > b;
}
int dfs(int pos, int r, int pre, bool limit, bool prezero)
{
    if (pos == -1) return (r == ns);
    if (!limit && !prezero && ~dp[pos][r][pre]) return dp[pos][r][pre];
    int end = limit ? bit[pos] : 9;
    int ans = 0;

    for (int i = 0; i <= end; i++)
    {
        if (prezero)
        {
            ans += dfs(pos - 1, r, i, limit && (i == end), prezero && (!i));
        }
        else
        {
            if (r < ns && ok(r, pre, i))///优先考虑向后拓展
                ans += dfs(pos - 1, r + 1, i, limit && (i == end), 0);
            else if (r > 0 && ok(r - 1, pre, i))
                ans += dfs(pos - 1, r, i, limit && (i == end), 0);
        }
        ans %= MOD;
    }
    if (!limit && !prezero) dp[pos][r][pre] = ans;
    return ans;
}
int calc(char a[], bool dec)
{

    int n = strlen(a);
    int len = 0, tmp = 0;
    while (a[tmp] == '0') tmp++;
    for (int i = n - 1; i >= tmp; i--)
    {
        bit[len++] = a[i] - '0';
    }
    if (dec && len > 0)
    {
        for (int i = 0; i < len; i++)
        {
            if (bit[i])
            {
                bit[i]--;
                break;
            }
            else bit[i] = 9;
        }
    }
    return dfs(len - 1, 0, 0, 1, 1);
}

int main ()
{
    while (scanf("%s", s) != EOF)
    {
        memset(dp, -1, sizeof(dp));
        ns = strlen(s);
        scanf("%s%s", a, b);
        printf("%08d\n", (calc(b, 0) - calc(a, 1) + MOD) % MOD);
    }
    return 0;
}

HDU 3709 平衡数

LL dp[20][20][2000];
///力矩最大为不超过10*20*10;
int bit[20];

LL dfs(int pos, int r, int sum, int e)
{
    if (pos == -1) return sum == 0;
    if (sum < 0) return 0;
    if (!e && ~dp[pos][r][sum]) return dp[pos][r][sum];
    int end = e ? bit[pos] : 9;
    LL ans = 0;
    for (int i = 0; i <= end; i++)
    {
        ans += dfs(pos - 1, r, sum + i * (pos - r), e && (i == end));
    }
    if (!e) dp[pos][r][sum] = ans;
    return ans;
}

LL calc(LL n)
{
    if (n < 0) return 0;
    int len = 0;
    while (n)
    {
        bit[len++] = n % 10;
        n /= 10;
    }
    LL ans = 0;
    for (int i = 0; i < len; i++)///需要枚举支点
        ans += dfs(len - 1, i, 0, 1);
    return ans - (len - 1);
}
int main ()
{
    memset(dp, -1, sizeof(dp));
    int t;
    LL l, r;
    RI(t);
    while (t--)
    {
        scanf("%I64d%I64d", &l, &r);
        printf("%I64d\n", calc(r) - calc(l - 1));
    }

    return 0;
}


HDU4352严格上升子序列的长度为K的个数。!!!

最长上升子序列结合,通过集合(1<<10)来处理

LL dp[25][1 << 11][11];
int bit[25];
int k;
int getnews(int s, int x)
{
    for(int i=x;i<10;i++)
        if(s&(1<>= 1;
    }
    return ret;
}
LL dfs(int pos, int s, int e, int z)
{
    if (pos == -1) return getnum(s) == k;
    if (!e && ~dp[pos][s][k]) return dp[pos][s][k];
    int end = e ? bit[pos] : 9;
    LL ans = 0;

    for (int i = 0; i <= end; i++)
    {
        int news = getnews(s, i);
        if (z && !i) news = 0;
        ans += dfs(pos - 1, news, e && (i == end), z && (!i));
    }
    if (!e) dp[pos][s][k] = ans;
    return ans;
}

LL calc(LL n)
{
    int len = 0;
    while (n)
    {
        bit[len++] = n % 10;
        n /= 10;
    }
    return dfs(len - 1, 0, 1, 1);
}

int main ()
{
    LL n, m;
    memset(dp, -1, sizeof(dp));
    int t;
    scanf("%d", &t);
    int nc = 1;
    while (t--)
    {
        cin >> n >> m >> k;
        printf("Case #%d: ", nc++);
        cout << calc(m) - calc(n - 1) << endl;
    }
    return 0;
}


!!!是比较不错,待看的题

比较还好的处理的题目:

Codeforces 55D Beautiful numbers!!!

spoj 10606 Balanced Numbers


ac自动机和数位dp结合(!!!):

hdu4376!!!(区间不可减???)
ZOJ3494 BCD Code(AC自动机+数位DP)!!!


整除和简单统计:

HDU4507 和7无关数的平方和!!!

HDU 3652 出现13,而且能被13整除

LightOJ 1068 能被K整数且各位数字之和也能被K整除的数

light OJ 1140两个数之间的所有数中零的个数。

lightoj 1032  二进制数中连续两个‘1’出现次数的和

其它:
LightOJ1205求区间[a,b]的回文数个数。
ural 1057 数位统计
codeforces215E周期数
codeforces258B在1-m中任选7个数,要使前六个数字中的“4”,"7"之和小于第七个的,
Zoj2599 数位统计(见题意)
zoj3162分形、自相似


你可能感兴趣的:(动态规划)