同余最短路通常用于处理这类问题:已知 $a_1,a_2,…,a_n$ ,求 $x_1,x_2,…,x_n$ 使得 $sum=\sum a_ix_i$ 属于某个区间或者在某个区间的最小值。
若这个区间很小,那么可以直接用完全背包解决。
若区间达到 $10^{18}$ 级别,那么就不能用背包,考虑问题转化。
易得 $sum=\sum_{i=2}^{n}a_ix_i+a_1x_1=sum_1+a_1x_1$ ,即把 $a_1$ 单独提出来, 最后的答案可由 $sum_1$ 加上很多个 $a_1$ 得到。因此只需考虑在模 $a_1$ 意义下能得到的最小的数,即 $d[(x+y)%a_i]=d[x]+y$ 。可转化为建边 $add(x,(x+a_i)%a_1,a_i)$ 然后跑最短路。
给出 $h,x,y,z$ ,求 $0-h$ 中有多少数可由多个 $x,y,z$ 组合得到。
同余最短路的模板。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #include<bits/stdc++.h> using namespace std; #define int long long
int h; int d[4]; int mod; int f[1000006]; vector<pair<int,int> > g[1000006];
signed main() { cin>>h>>d[1]>>d[2]>>d[3]; sort(d+1,d+4); mod=d[1];
for(int i=0;i<mod;i++) { g[i].push_back({(i+d[2])%mod,d[2]}); g[i].push_back({(i+d[3])%mod,d[3]}); }
memset(f,0x3f,sizeof(f)); priority_queue<pair<int,int> > q; q.push({0,0}); f[0]=0;
while(q.size()) { int u=q.top().second; q.pop(); for(auto i:g[u]) { int v=i.first; int w=i.second; if(f[v]>f[u]+w) { f[v]=f[u]+w; q.push({-f[v],v}); } } }
int ans=0; for(int i=0;i<mod;i++) { if(f[i]>h)continue; ans+=(h-1-f[i])/d[1]+1; } cout<<ans; return 0; }
|
找到一个数,使得它各位数字和是最小的且满足是 $k$ 的倍数。
注意到一个数,若乘十,那么数字和不变,若加一,数字和增加一,因此有如下方法:
将小于 $k$ 的每个数,建边 $add(i,(i+1)%k,1),\ add(i,(i*10)%k,0)$ ,然后跑最短路,从 $d[1]$ 开始,最后的答案是 $d[0]+1$ 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include<bits/stdc++.h> using namespace std;
int n; vector<pair<int,int> > g[100005]; int d[100005];
int main() { cin>>n; for(int i=0;i<n;i++) { g[i].push_back({(i+1)%n,1}); g[i].push_back({i*10%n,0}); }
memset(d,0x3f,sizeof(d)); d[1]=0; priority_queue<pair<int,int> > q; q.push({0,1});
while(q.size()) { int u=q.top().second; q.pop(); for(auto i:g[u]) { int v=i.first; int w=i.second; if(d[v]>d[u]+w) { d[v]=d[u]+w; q.push({-d[v],v}); } } } cout<<d[0]+1; return 0; }
|