题目描述:
对一个数字进行每一位加一的操作,如果进位,则添加一位。例如9876进行一次操作就会变成10987,再来一次就是211098,以此类推。输入数字和操作次数,输出最后数字的位数。
有 T < 2e5 个测试用例,数字小于1e9,操作次数小于2e5
思路:
如果使用单纯的暴力模拟,时间复杂度为O( n ^ 2 ),数量级到了1e10,时间不支持,所以我们需要另寻办法,仔细思考我们可以发现,对一个数字来说,它进行了n次操作后得到的数字是一定的,不会受到其他的数字影响。而且操作次数的数量级也不是特别的大,我们就可以先将0~9进行N次操作后会达到多少位存进一个数组里面,然后将输入的数字的每一位最后得到的数位加起来取个模就可以了。
那我们应该算出对应的数组呢,首先,0~9的结果是肯定的,都是1位。然后我们发现,对于一个数字,只要进行10 - a 次操作之后必定会多出一位,变成10。然后出现的1和0又会在9次和10次之后再次变成10.那么我们就可以得到转换关系,每次出现进位的时候就将9位和10位后面的数做出标记,如果遇到有标记的地方就再次进行这个操作。这里用了桶来标记,实际上可以在同一个数组完成这个操作,只须先检测该位是否为0即可。(原本我用的队列,笑死,根本不行)
时间复杂度为O( n )
代码:
#include <bits/stdc++.h>
#define MOD 1000000007
using namespace std;
int numplus[ 200010 ];
int saber[ 20 ];
int bucket[ 200100 ];
int main() {
int T;
char temp;
int i, j, k, p;
long ans;
/*plusnum.push(10); 这里是原本想要用队列做发现不行
因为用队列无法保证队列里的序号是顺序的
用优先队列的话会超时
numplus[ i ] = 1;
for ( i = 1; i < 200010; i++ ) {
numplus[ i ] = numplus[ i - 1 ];
lable :
if ( i == plusnum.front() ) {
plusnum.push( i + 9 );
plusnum.push( i + 10 );
plusnum.pop();
numplus[ i ] ++;
numplus[ i ] %= MOD;
goto lable;
}
}*/
numplus[ 0 ] = 1;
bucket[ 10 ] ++ ;
for ( i = 1 ; i < 200010 ; i ++ ) {
numplus[ i ] = numplus[ i - 1 ];
if ( bucket[ i ] ) {
bucket[ i + 9 ] += ( bucket[ i ] % MOD ) ;
bucket[ i + 10 ] += ( bucket[ i ] % MOD );
numplus[ i ] += ( bucket[ i ] % MOD );
numplus[ i ] %= MOD;
}
}
cin >> T;
while ( T -- ) {
ans = 0, p = 0;
getchar();
temp = getchar();
while ( temp != ' ' ) {
saber[ ++p ] = temp - '0';
temp = getchar();
}
cin >> i;
for ( j = 1 ; j <= p ; j++ ) {
ans += numplus[ saber[ j ] + i ];
ans %= MOD;
}
cout << ans << '\n';
}
}