D125 同余最短路 Dijkstra 算法 AT_arc084_b [ABC077D] Small Multiple
D125 同余最短路 AT_arc084_b [ABC077D] Small Multiple_哔哩哔哩_bilibili
AT_arc084_b [ABC077D] Small Multiple - 洛谷
给定一个整数 K,求一个 K 的正整数倍 S,使得 S 的数位之和最小,输出数位之和。
思路
考虑一个整数 $x$ 变成更大的整数 $10x+j,\;j\in [0,9]$,数位之和增加 $j$
建图表示:$x\overset{j}{\rightarrow} 10x+j,\;j\in [0,9]$,边权就是数位和的增量
所有整数按模 $k$ 可以分为 $k$ 个同余类,模 $k$ 建图:$i\overset{j}{\rightarrow} (10i+j)\%k,\;i\in [0,k-1],\;j\in [0,9]$
注意:起点不是 0 点,起点是 $k$ 点,可以从 $k$ 点向 $(10k+1)\%k$ 点连一条边权为 $1$ 的边
跑同余最短路后,$d[0]$ 表示模 $k$ 为 $0$ 的点到起点 $k$ 的最短路(即数位之和的最小值)
相关板子:
D123【模板】同余最短路 Dijkstra 算法 P2662 [WC2002] 牛场围栏 - 董晓 - 博客园
// 同余最短路 Dijkstra 算法 O(MlogN) #include<bits/stdc++.h> #define int long long #define pii pair<int,int> using namespace std; const int N=100010,M=N*10; int idx,h[N],to[M],ww[M],ne[M]; void add(int u,int v,int w){ to[++idx]=v;ww[idx]=w;ne[idx]=h[u];h[u]=idx; } int k,d[N]; void dijkstra(int s){ memset(d,0x3f,sizeof(d)); d[s]=0; priority_queue<pii,vector<pii>,greater<pii> >q; q.push({0,s}); while(!q.empty()){ auto [dd,u]=q.top();q.pop(); if(dd!=d[u]) continue; for(int i=h[u];i;i=ne[i]){ int v=to[i],w=ww[i]; if(d[v]>d[u]+w){ d[v]=d[u]+w; q.push({d[v],v}); } } } } signed main(){ scanf("%lld",&k); for(int i=0;i<k;++i)for(int j=0;j<10;++j)add(i,(i*10+j)%k,j); add(k,(k*10+1)%k,1); //起点k dijkstra(k); cout<<d[0]; }
更简单的做法,使用 $01BFS$ 求最短路
假设 $x\; mod\; k=0$,且 $x$ 的数位之和 $s$ 最小
从 $x=1,s=1$ 开始扩展,有两种操作:
1. $x∗=10$,$x$ 的数位之和 $s$ 不变,代价为 0,把状态 $(x,s)$ 放在队首
2. $x+=1$,$x$ 的数位之和变为 $s+1$,代价为 1,把状态 $(x,s+1)$ 放在队尾
每次操作保存 $x\%k$ 的状态即可
第一次扩展到 $x\; mod\; k=0$,那么对应 $s$ 就是答案
// 同余最短路 01BFS O(N) #include<bits/stdc++.h> using namespace std; const int N=1e5+5; bool vis[N]; int k; void bfs(){ deque<pair<int,int> > q; q.push_front({1,1}); //从{1,1}开始扩展 while(!q.empty()){ auto [x,s]=q.front(); q.pop_front(); if(vis[x]) continue; vis[x]=true; if(x==0){ printf("%d\n",s); return; } q.push_front({x*10%k,s}); q.push_back({(x+1)%k,s+1}); } } int main(){ scanf("%d",&k); bfs(); }
浙公网安备 33010602011771号