题目大意:
给出n,问从0到n有多少数对(A,B),满足A<B,但是A的数位之和大于B的数位之和?
解题思路:
数位dp。之前使用数位dp,都是求解一个范围[L,R]内满足某个条件的数有多少。这次是求满足条件的对数。
学到的第一个是用空间换时间,就是把lima,limb之类的都写进dp里,形成一个5维dp
dp[pos][sum][lima][limb][limit]
分别代表:
1.当前搜索到的位置
2.前面已经搜索出来的各位之差
3.第一个数最高位此时是否有限制
4.第二个数最高位此时是否有限制
5.第二个数在前面是否已经大于了第一个数
普通的数位dp是在dfs的时候构造一个满足条件的数,而这里的数位dp在dfs的时候通过枚举位置同时构造了两个数A,B,并且在其中维护A<B。于是dfs就同时枚举两个数在pos位置的数字。最后dfs出去的条件是sum是否大于基数,如果大于基数代表A的数位之和大于B的数位之和。那么就得到一组满足条件的(A,B)。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
const int base = 1000;

string str;

ll dp[110][2005][2][2][2];
/*1.当前搜索到的位置
2.前面已经搜索出来的各位之差 
3.第一个数最高位此时是否有限制
4.第二个数最高位此时是否有限制
5.第二个数在前面是否已经大于了第一个数 */
ll dfs(int pos,int sum ,bool lima, bool limb, bool limit)
{
    if(pos < 0) {
        return sum > base;    
    }
    if(dp[pos][sum][lima][limb][limit] != -1) return dp[pos][sum][lima][limb][limit];
    int up1 = lima ? str[pos] - '0' : 9;
    int up2 = limb ? str[pos] - '0' : 9;
    ll ans = 0;
    for(int i = 0; i <= up1; i++) {
        for(int j = 0; j <= up2; j++) {
            if(i > j && !limit)
                continue;
            ans += dfs(pos-1,sum+i-j ,lima && (i==up1), limb &&(j==up2), limit||(j>i));
            ans %= mod; 
        }
    }
    return dp[pos][sum][lima][limb][limit] = ans;
}

int main()
{
    memset(dp, -1, sizeof(dp));
    cin >> str;
    int len = str.length();
    int L = 0, R = len-1;
    while(L < R)
    {
        swap(str[L++],str[R--]);
    }

    cout << dfs(len - 1,base ,1, 1, 0) << endl;
}