题目描述:
给定一个数 x,设它十进制展从高位到低位上的数位依次是 a0, a1, ..., an - 1,定义交错和函数:
f(x) = a0 - a1 + a2 - ... + ( - 1)n - 1an - 1
例如:
f(3214567) = 3 - 2 + 1 - 4 + 5 - 6 + 7 = 4
给定
输入数据仅一行包含三个整数,l, r, k(0 ≤ l ≤ r ≤ 1018, |k| ≤ 100)。
输出一行一个整数表示结果,考虑到答案可能很大,输出结果模 109 + 7。
对于样例 ,满足条件的数有 110 和 121,所以结果是 231 = 110 + 121。
更多样例:
Input |
4344 3214567 3 |
Output |
611668829 |
Input |
404491953 1587197241 1 |
Output |
323937411 |
Input |
60296763086567224 193422344885593844 10 |
Output |
608746132 |
Input |
100 121 -1 |
Output |
120 |
100 121 0样例输出
231
解题思路:
这道题确实花了我好几天的时间来做。。首先,根据题目的意思,我尝试列举几个样例来得到一般规律,但处处因为各种原因(比如说当在某一位发生进位时。。),导致分析过程异常复杂,而且情况也多种多样,最后不得已,只好放弃找规律这条路。理所当然地,一般规律找不到,就只有暴力来解决,下面是代码:
#include
#include
#include
using namespace std;
int MOD = 1e9+7;
/*int cross_sum(long long t){
int res = 0;
strstream ss;
ss << t;
string num;
ss >> num;
int flag = 1;
for(int i = 0; i < num.size(); i++){
res += flag * (num[i] - '0');
flag = -flag;
}
return res;
}*/
int cross_sum(long long t){
int res = 0, flag = 1;
while(t > 0){
res += flag * (t % 10);
t = t/10;
flag = -flag;
}
if(flag > 0)
res = -res;
return res;
}
int main(){
//freopen("t.txt", "r", stdin);
long long left, right;
int k;
scanf("%lld%lld%d", &left, &right, &k);
long long res = 0, base = left;
for(; base <= right; base++){
if(cross_sum(base) == k){
res = (res + base) % MOD;
}
}
printf("%lld\n", res);
return 0;
}
当然了,这个代码是超时的,当它在我后面的调试中起到很重要的作用!(输出结果,用excel来处理,看看与自己代码输出是否一致)
现在来仔细分析这个代码的复杂度,可以粗略地认为它的时间复杂度为O(n),n为输入的值,可以看到n可以达到10^20的数量级,这确实是很难被接受的,不得已,在网上搜了下,发现这道题是用数位dp来做的,由于之前没怎么接触过,所以就找了道题做了下,做完之后,再来看这道题,就基本有思路了,所以就尝试开始做,这里我采用的是非递归方法,所以会比递归的dfs来做看起来会复杂点,这在之前已经讨论过了,基本思路如下:
dpSum[i][j] 表示长度为i,交错和为j的数的和(注意,这里的数可以以0开头,否则就会少算一些值,例如当确定了最高位是1后,其余i-1位可以以0开头)
dpCnt[i][j] 表示长度为i, 交错和是j的数的个数
由于交错和可以是负数,所以为其加上100,保证其为正数,状态转移方程为:(在程序中要注意溢出问题,这里为了便于理解,就省去了取模操作)
dpSum[i][j] += dpSum[i-1][200-(j-k)] + k * sz[i] * dpCnt[i-1][200-(j-k)]
其中k取0到9,表示第i位可能的取值,200-(j-k)表示当前的交错和减去第i位的值再取反,sz[n]表示位数为n的最小的数(这里将其作为基底),即当n为2时,sz[n]为10,
dpCnt[i][j] += dpCnt[i][200-(j-k)];
初始状态是:dpCnt[0][0] = 1(这一点很重要),然后将长度为1的值都初始化就可以了。
求完dp数组后,就可以求具体的值了,这里用了个cal函数long long cal(long long n, int target) 表示求0到n-1的交错和为k的数的和:
首先先求出位数少于n的位数的结果,
然后求位数等于n的位数的数的和:
res += dpSum[i-1][200-(target-k)] + (n-n%sz[i+1]+k*sz[i]) * dpCnt[i-1][200-(target-k)] ;
其中 (n-n%sz[i+1]+k*sz[i]) 求出的是该数的比i高的位,和以k作为第i位的数,例如1234567,i = 5,k = 2则这里表示1220000。
整体代码如下,
#include
#include
#include
using namespace std;
const int N = 22, MAX = 209, MOD = 1e9+7;
long long dpSum[N][MAX], dpCnt[N][MAX], sz[N] = {0,1};
void init(){
memset(dpSum, 0, sizeof(dpSum));
memset(dpCnt, 0, sizeof(dpCnt));
for(int i = 2; i < N; i++){
sz[i] = sz[i-1] * 10;
}
dpCnt[0][100] = 1;
for(int i = 0; i <= 9; i++){
dpSum[1][100+i] = i;
dpCnt[1][100+i] = 1;
}
for(int i = 2; i < N; i++){
for(int j = 0; j <= 200; j++){
for(int k = 0; k <= 9; k++){
dpSum[i][j] += (dpSum[i-1][200-(j - k)]
+ k * sz[i] % MOD * dpCnt[i-1][200-(j - k)])%MOD;
dpSum[i][j] %= MOD;
dpCnt[i][j] += dpCnt[i-1][200-(j - k)];
dpCnt[i][j] %= MOD;
}
}
}
}
long long cal(long long n, int target){
if(n == -1)
return 0;
int numArray[N], len = 0;
long long tmp = n;
while(tmp){
numArray[++len] = tmp % 10;
tmp /= 10;
}
long long res = 0;
//先求出满足条件的位数小于n的数字的和
for(int i = 1; i < len; i++){
for(int k = 1; k <= 9; k++){
res += ( dpSum[i-1][200-(target-k)]
+ k * sz[i] % MOD * dpCnt[i-1][200-(target-k)] % MOD ) % MOD;
res %= MOD;
}
}
//求位数等于n的位数的满足条件的数字和
for(int i = len; i > 0; i--){
int k = (i == len);
for(; k < numArray[i];k++){
res += (dpSum[i-1][200-(target-k)]
+ (n-n%sz[i+1]+k*sz[i]) % MOD * dpCnt[i-1][200-(target-k)] % MOD ) % MOD;
res %= MOD;
}
target = 200 - (target - numArray[i]);
}
return res;
}
int main(){
//freopen("t.txt", "r", stdin);
init();
long long left, right;
int k;
scanf("%lld%lld%d", &left, &right, &k);
long long res = cal(right+1, k+100) - cal(left, k+100);
if(res < 0)
res += MOD;
res %= MOD;
printf("%lld\n", res);
return 0;
}
当然,这道题也可以用递归来做:
其中,主要注意dp[i][j]保存的是不考虑上下界,且可以以0开头的i位数,其交错和是j的数的和与个数;
node dfs(int pos, int target, int hasCeiling, int len)
返回的是在长度为len,从0到pos位,交错和为target的数的和与个数
#include
#include
#include
using namespace std;
const int N = 22, MAX = 209, MOD = 1e9+7;
long long base[N] = {0,1};
int numArray[N];
struct node{
long long sum, cnt;
node():sum(-1), cnt(-1){}
};
node dp[N][MAX];
node dfs(int pos, int target, int hasCeiling, int len){
node res;
res.sum = res.cnt = 0;
if(pos == 0){
if(target == 100)
res.cnt = 1;
return res;
}
//注意dp中保存的是没有上下限限制,且开头可以为0的数
if(!hasCeiling && len != pos && dp[pos][target].cnt != -1)
return dp[pos][target];
int head = (len == pos);
int tail = hasCeiling ? numArray[pos] : 9;
for(int i = head; i <= tail; i++){
node tmp = dfs(pos-1, 200-(target-i), hasCeiling && i==numArray[pos], len);
if(tmp.cnt > 0){
res.cnt += tmp.cnt;
res.cnt %= MOD;
res.sum += tmp.sum;
res.sum %= MOD;
res.sum += (tmp.cnt * i * base[pos]) % MOD;
res.sum %= MOD;
}
}
if(!hasCeiling && len != pos){
dp[pos][target] = res;
}
return res;
}
long long cal(long long n, int k){
if(n == -1)
return 0;
int len = 0;
while(n){
numArray[++len] = n % 10;
n /= 10;
}
long long ans = 0;
node tmp;
for(int i = 1; i <= len; i++){
tmp = dfs(i, k, i == len, i);
ans += tmp.sum;
ans %= MOD;
}
return ans;
}
int main(){
//freopen("t.txt", "r", stdin);
for(int i = 2; i < N; i++)
base[i] = base[i-1] * 10 % MOD;
long long left, right;
int k;
scanf("%lld%lld%d", &left, &right, &k);
/*
cout << cal(right, k+100) << endl;
cout << cal(left-1, k+100) << endl;
*/
printf("%lld\n", (cal(right, k+100) - cal(left-1, k+100) + MOD) % MOD );
return 0;
}