(同余最短路)[arc084_b]Small Multiple
写在前面
先吐槽一下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;
}

浙公网安备 33010602011771号