题目传送门。
题意:给定一个整数 \(k\),求一个 \(k\) 的正整数倍 \(s\),使得 \(s\) 的数位和最小。
首先有一个基本结论:任意正整数都可以从 \(1\) 开始,经过若干次 \(+1\) 和 \(\times 10\) 得到。
而进一步观察,可以发现前一种操作会使数位和增加 \(1\),后一种操作不影响数位和。那么一个正整数的数位和,实际上就是它从 \(1\) 变化而来的所有方式中,最小的 \(+1\) 次数再加 \(1\)(因为起点是 \(1\))。显然我们要尽可能多地 \(\times 10\),才能找到这一次数。
举例来说,\(91\) 可以是从 \(1\) 开始,连加 \(90\) 个 \(1\) 而得到的;也可以是先做 \(8\) 次 \(+1\) 操作,再乘以 \(10\),最后 \(+1\) 而得到的。可以发现,后一种方式只需要 \(9\) 次 \(+1\),且不存在 \(+1\) 次数更少的方式。因此可以发现,\(91\) 的数位和是 \(10\)。
接下来把这一性质运用到本题中。由“最小的 \(+1\) 次数”,联想到以某种方式建出一张图,然后求最短路。
我们要凑的是 \(k\) 的倍数,因此我们将所有正整数在模 \(k\) 意义下分类,同余的分为一类,即通常所说的“同余类”。这样我们就得到了 \(k\) 个同余类,其中每个同余类的编号定义为这里面的数模 \(k\) 的余数。
接下来,将每个同余类视作图中的一个结点,记为 \(u\)。对于 \(\forall u \in [0,k-1]\cap\mathbb{Z}\),我们建出有向边 \(u\rightarrow (u+1)\bmod k\) 和 \(u\rightarrow (10u)\bmod k\)。由之前的结论,前者的边权为 \(1\),后者的边权为 \(0\)。
显然 \(k\) 的倍数位于 \(0\) 号同余类中,因此要找到数位和最小的 \(k\) 的倍数,我们只需要在这张图上跑出 \(1\) 到 \(0\) 的最短路。最短路的长度就是数位和。
代码(题中的 \(k\) 在代码里改成了 \(n\),注意鉴别):
#include<iostream>
#include<queue>
using namespace std;const int N=1e5+5;int n;namespace graph{#define rep for(int i=head[u];~i;i=e[i].nxt)#define handle int v=e[i].v,w=e[i].w;int idx=-1;int head[N];struct edge{int v,nxt,w;}e[N<<3];inline void add(int u,int v,int w){return e[++idx]={v,head[u],w},head[u]=idx,void();}int dis[N];bool vis[N];inline void SPFA(int s){for(int i=0;i<N;++i)dis[i]=1e9;queue<int>q;q.push(s),dis[s]=1;while(!q.empty()){int u=q.front();q.pop(),vis[u]=0;rep{handle;if(dis[v]>dis[u]+w){dis[v]=dis[u]+w;if(!vis[v])vis[v]=1,q.push(v);}}}return ;}#undef rep#undef handle}using namespace graph;signed main(){for(int i=0;i<N;++i)head[i]=-1;ios::sync_with_stdio(false);cin.tie(0);cin>>n;for(int i=0;i<n;++i)add(i,(i*10)%n,0),add(i,(i+1)%n,1);return SPFA(1),cout<<dis[0]<<"\n",0;
}
提交记录。