题目大意:
给出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;
}
京公网安备 11010502036488号