(同余最短路)[arc084_b]Small Multiple

HZOJ
洛谷

写在前面

先吐槽一下HZOJ屎一样的翻译,时隔如此久还能雷到我。翻译完全和题目不搭边。还好有说人话的实际是简体中文的繁体中文。

\[Run, run~run~higher, fly, fly~fly~ higher, I, I~find~myself. \]

[arc084_b]Small Multiple 题解

题意

给定一个数字\(k\),求\(k\) 的倍数中十进制下各位数之和的最小值。

思路

乍一看像是数位dp,然鹅题解区貌似没有数位dp的做法。能产生这样的误解大概就是因为笨人学艺不精了(关于数字位数加上最小值。数位+类dp不就是数位dp吗qwq)。

本题属于那种自己写死活想不出来并且方向也大概率是错的,看题解能立马明白的那种巧妙但思维难度大的题。

本题正解应该是同余最短路。首先考虑到在每个数后面加一个0,对答案产生的贡献也是0,所以可以向该数的10倍连一条边权为0的边。相邻两个数的贡献之差为1,所以可以从\(i\)\(i+1\) 连一条边权为1的边。至于多连了类似于从9到10这样的贡献差不为1的边,因为在像1这样的点已经有一条边权为0的边到10,所以从1到9到10一定不优一定不会走,所以不会对答案产生影响。最后从1开始跑最短路,输出到0点即模\(k\) 为0即\(k\) 的倍数的最小距离,由于巧妙地通过连边和边权的选择,距离即为该位置对应的数对答案的贡献。

代码实现

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int k;
int tot,to[maxn<<1],nxt[maxn<<1],h[maxn],val[maxn<<1];
inline void adde(int x,int y,int z){
	to[++tot]=y;
	nxt[tot]=h[x];
	val[tot]=z;
	h[x]=tot;
}
int dis[maxn];
bool vis[maxn];
priority_queue<pair<int,int>>q;
void dij(int x){
	memset(dis,0x3f,sizeof(dis));
	dis[x]=1;
	q.push({0,x});
	while(q.size()){
		x=q.top().second;
		q.pop();
		if(vis[x]) continue;
		vis[x]=1;
		for(int i=h[x];i;i=nxt[i]){
			int y=to[i];
			if(dis[y]>dis[x]+val[i]){
				dis[y]=dis[x]+val[i];
				q.push({-dis[y],y});
			}
		}
	}
}
int main(){
	cin>>k;
	for(int i=0;i<k;i++)
	  adde(i,(i+1)%k,1),adde(i,(10*i)%k,0);
	dij(1);
	cout<<dis[0];
	return 0;
}
posted @ 2025-08-17 07:46  _dlwlrma  阅读(11)  评论(0)    收藏  举报