Educational Codeforces Round 90 (Rated for Div. 2) E.Sum of Digits(思维题/贪心+数位和)

题目

计f(x)为x的数位和(如f(102)=1+0+2),t(t<=150)组样例,

每次给出n(n<=150)和k(0<=k<=9),求满足f(x)+f(x+1)+...+f(x+k)=n的最小的非负整数x

思路来源

jiangly代码

题解

①(打表假题)共k+1个数,则数位和这种东西是\sqrt[k+1]{n}级别的,

设n=150,则k=0大致为1e18(9*17=153),k=1就降到1e9了,k=2就到1e6了,k=3四个数就几万了……

k=0直接贪心构造,k=1和k=2本地暴力打表,k>=3直接for现算,记忆化一下就过去了…

 

②(构造)由于不超过10个数,没有末一位相同的数,

枚举最后一位las,这样能算出需要进位的数的个数add,

而由于只有9进位后会变成0,在填9时只有(k+1-add)个产生了9,其余均为0,

有数位和损失,故将其与0-8分开考虑,

 

0-8是等价的,因为均不产生进位,k+1个8可以写作k+1个17、26,等等

不难发现,这个数最小,则从低位到高位构造时,仍需的数位和,递减的越快越好,

最后的答案的一种情况应该是,一个首位X+中间一段9+一个8+末尾一段9+枚举的最后一段las

 

填一些进位的9之后,剩下的位是不进位的,可以先填一个8,然后就可以填9了

而出现了这个8之后,前面的一段9就不需要进位了,有k+1个数产生贡献,更优

 

这题开始以为只有枚举末位和中间一段9,没考虑到989的情形,

样例44 1就说明了这一点,答案是989,而不是1889

心得

①赛中WA了之后,可以考虑用暴力n<=50和k<=9在小范围内对拍,这样容易发现问题

②发现一些小性质吧,如本题答案不超过1e18,这样就不会在赛中用string乱搞了……

代码

#include
using namespace std;
typedef long long ll;
int t,n,k;
ll ans;
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&k);
        //枚举最后一位
        ans=1e18;//1e17 9*17=153
        for(int las=0;las<=9;las++){
            //枚举进位9的个数
            //统计最后一位 中间进位9 总共的数位和
            for(int nine=0;nine*9<=(n-las);++nine){
                int add=0,sum=0;//进位的个数
                for(int i=0;i<=k;++i){
                    int now=las+i;
                    if(now>=10)add++;
                    sum+=now%10;
                }
                sum+=nine*9*(k+1-add)+add;//进位的数的贡献 不进位的贡献
                //printf("las:%d nine:%d sum:%d\n",las,nine,sum);
                sum=n-sum;
                if(sum<0 || sum%(k+1))continue;//高位均分k+1份
                sum/=(k+1);
                int mid,pre;//最高位pre
                ll tmp=0;
                if(sum<=8){
                    mid=sum;
                }
                else{
                    mid=8;
                    sum-=mid;
                    pre=sum%9;
                    sum-=pre;
                    tmp=pre;
                    for(int l=1;l<=sum/9;++l){//中间一段不进位的9
                        tmp=tmp*10+9;
                    }
                }
                tmp=tmp*10+mid;//填一个8
                for(int l=1;l<=nine;++l){//后面一段进位的9
                    tmp=tmp*10+9;
                }
                tmp=tmp*10+las;//末位枚举的las
                ans=min(ans,tmp);
            }
        }
        printf("%lld\n",ans==1e18?-1:ans);
    }
    return 0;
}

 

你可能感兴趣的:(思维题)