题目描述
输入描述:
输出描述:
示例1
输入
100
输出
967
题目大意
定义一个函数 S ( n ) S(n) S(n)表示 n n n的各位数之和。现要求在 1 ∼ n 1\sim n 1∼n之间有多少数对 ( a , b ) (a,b) (a,b)满足 S ( a ) > S ( b ) S(a)>S(b) S(a)>S(b),并且 0 ≤ a ≤ b ≤ n 0\le a\le b\le n 0≤a≤b≤n。
分析
十分亮眼的是,
n
≤
1
0
100
n\le 10^{100}
n≤10100。又看到各位数之和,自然想到数位
d
p
dp
dp。这题主要是有两个数要进行数位
d
p
dp
dp,所以比较烦人。但是代码是短的
先考虑 d p dp dp的定义,第一维肯定是 p o s pos pos表示第 p o s pos pos位,然后考虑两个数的 s u m sum sum,但是两个数各位的 s u m sum sum最大有 900 900 900, 900 ∗ 900 ∗ 100... 900*900*100... 900∗900∗100...这样的空间肯定是炸了啊,因此考虑压缩。
因为文中只要比较大小即可,所以我第二维只要存一下 a a a和 b b b的差就可以了。为了防止差为负数,要加1000的偏移量。
因此, d p [ p o s ] [ d i f f ] [ l m t a ] [ l m t b ] dp[pos][diff][lmta][lmtb] dp[pos][diff][lmta][lmtb]表示 p o s pos pos位时, a , b a,b a,b的差为 d i f f diff diff, a a a是否到极限, b b b是否到极限的满足题意的数对数目。
转移式见代码注释。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=110;
const int mod=1e9+7;
char N[MAXN];
int dp[MAXN][MAXN*20][2][2],n[MAXN];
int dfs(int pos,int diff,bool lmta,bool lmtb){
// cerr<<pos<<' '<<diff<<endl;
if(!pos) return diff>1000;//由于偏移,因此0变成1000
if(~dp[pos][diff][lmta][lmtb]) return dp[pos][diff][lmta][lmtb];
//记忆化
int ret=0;
for(int i=0;i<=(lmtb?n[pos]:9);i++){//枚举b的pos位,如果是极限,那么应当是n的pos位为极限
for(int j=0;j<=(lmta?i:9);j++){//枚举a的pos位,同上
ret=(ret+dfs(pos-1,diff+j-i,lmta&(j==i),lmtb&(i==n[pos])))%mod;
//加上a减去b,若a>b则其值为正,并更新极限
}
}
return dp[pos][diff][lmta][lmtb]=ret;//记忆化
}
int main()
{
memset(dp,-1,sizeof(dp));//初始化-1
scanf("%s",N);
int len=strlen(N);
for(int i=0;i<len;i++) n[len-i]=N[i]-'0';//字符串输入,塞到数组里。
printf("%d\n",dfs(len,1000,1,1));//调用
}
END
难得,比较顺利地AC了一道 d p dp dp。