题目描述
Find the smallest possible sum of the digits in the decimal notation of a positive multiple of K.
Constraints
2≤K≤105
K is an integer.
输入
Input is given from Standard Input in the following format:
K
输出
Print the smallest possible sum of the digits in the decimal notation of a positive multiple of K.
样例输入
6
样例输出
3
提示
12=6×2 yields the smallest sum.
题意:给出一个数k,找到一个k的正整数倍数(k*i)使得这个倍数的每一数位上的值加和最小。如6的2倍是12,1+2=3,是所有倍数中各位加和最小的。
思路:通过广搜实现,首先这题一开始想的针对数字和倍数找数学规律的方法是不可取的,观察了各种数的倍数特征后发现并没有什么规律,并且因为k值很大,因此不能通过直接遍历i然后check最小按位加和的方法来筛选数字。
那么通过不一样的想法,因为是按位加和,因此我们应该从最小的按位加开始查找,如1,10,100,1000…..然后是11,1100,11000,101,1010,1011,2,20,220,21,可以发现,我们对于按位数字加和会使数字出现,某一位上值+1,或整体数字*10的操作,一个数字乘10,按位加和不会改变,一个数字+1,按位加和改变,就能一个不漏的按序找到一些加和较小的值。
接下来是判断该数是否是k的倍数,在搜索的过程中得到了一个新的数,这个数直接对k取模,如果为0即k的倍数,那么这个答案就是符合条件的。但是要注意,因为广搜的过程中一个数会出现两个分支的搜索路径,一个+1,一个*10,很明显如果我们要求加和最小就应该先遍历*10的结果是否符合条件,因为这是不改变加和的状态,在此基础上,一个数完成了所有*10的搜索,才能遍历下一个+1的搜索。这就需要双向队列对搜索进行分类,右端插入+1分支,左端插入*10分支,一直从*10端取数判断,并且遍历过程中标记数是否检查过,因为结果一直对k取模,所以遍历的数不会超过k值大小。
其中7的答案你以为是3吗?7 * 3=21,1+2=3?太天真了!其实是2,因为7 * 143=1001
还有一个问题是,你是否认为,我虽然说的操作是对每个数按位*10和+1,但投入到队列中搜索的数其实是对k取模过的,因此并不准确,如我想搜索的是21,那么当2*10的时候塞入队列的其实是20%7=6,这样一来下次想要遍历20+1的 时候其实遍历到的是6+1=7,连按位加和的2都与6对不上了,但其实这个取模操作是能保证运算正确的,如10%7=3,投入3之后,下次想要遍历100,那么取出3,然后3*10=30,再取模7,得到30%7=2,这与100%7=2的结果是一样的,不会造成影响。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;
bool vis[maxn];
struct node
{
int num,sum;
node() {}
node(int a,int b)
{
num=a;
sum=b;
}
};
deque<node>q;///双向队列保证搜索顺序,因为要先遍历了再同一个按位加和的情况下,所有乘10的情况,再去计算下一个按位加和是否是倍数
int k;
int main()
{
scanf("%d",&k);
memset(vis,false,sizeof vis);
q.clear();
q.push_back(node(1,1));
while(!q.empty())
{
node tmp=q.front();///前端表示尽量保持按位加的变化
q.pop_front();
if(!vis[tmp.num])
{
vis[tmp.num]=true;///标记已经遍历过的数
if(!tmp.num)///当对k的取模已经为0且第一个搜到的,表示按照最小按位加且是k的倍数
{
printf("%d\n",tmp.sum);
break;
}
q.push_back(node((tmp.num+1)%k,tmp.sum+1));///后端表示,当前端都被标记过后,从后端取出+1的按位和来继续搜索所有*10的情况
q.push_front(node((tmp.num*10)%k,tmp.sum));
}
}
}