好题集 (5) - AT ARC084B Small Multiple

题目传送门

题意:给定一个整数 \(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;
}

提交记录

posted @ 2025-11-16 10:36  DX3906_ourstar  阅读(6)  评论(0)    收藏  举报