同余最短路
同余最短路
与差分约束类似,都是最短路的一些神奇应用。
同余最短路可以解决类似这样的问题:
- 给定 \(n\) 个数,求给定值域内,用这些数(可以重复取)能够拼出多少其他数。代表题目:P3403、P2371。
- 给定 \(n\) 个数,求这 \(n\) 个整数不能拼出的最小/最大整数。代表题目:P2662。
- 给定 \(n\) 个数,至少要拼几次才能拼出模 \(K\) 余 \(p\) 的数。代表题目:Unknown。
P3403 跳楼机
为了方便,我们把楼层的值域从 \([1,h]\) 调整为 \([0,h-1]\)。
不妨假设 \(x<y<z\),令 \(\text{dis}_i\) 为只通过 \(y\) 和 \(z\),需满足 \(p\bmod x=i\) 的最低楼层 \(p\),也就是只用 \(y\) 和 \(z\) 能拼出的模 \(x\) 与 \(i\) 同余的最小数。可以得到两个状态:
- \(i\xrightarrow{y}(i+y)\bmod x\)
- \(i\xrightarrow{z}(i+z)\bmod x\)
就按照这两个状态建边,跑最短路,可以求出我们刚才所说的 \(\text{dis}_i\),其中 \(i\in[0,n)\)。接下来考虑怎么用 \(\text{dis}_i\) 统计答案,因为 \(\text{dis}_i\) 是满足模 \(x\) 与 \(i\) 同余的最低楼层,所以用值域减去 \(\text{dis}_i\),再除以 \(x\) 并向下取整,就是它能提供的贡献。还要 \(+1\),因为 \(d_i\) 所在楼层也算一次。
用公式表示出来如下:
容易发现最后的时空复杂度只和 \(x\) 有关,这也是为什么 \(x\) 越小越好。
然后这题就做完了。注意细节,\(h<2^{63}\),而 long long 的最大范围是 \(2^{63}-1\),所以得开 unsigned long long。
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
using pii=pair<int,int>;
constexpr int MAXN=1e5+5;
int h,x,y,z;
vector<pii>g[MAXN];
int dis[MAXN];
bool vis[MAXN];
void dijkstra(int s){
for(int i=1;i<x;++i) dis[i]=ULONG_LONG_MAX;
priority_queue<pii,vector<pii>,greater<pii>>q;
q.emplace(0,s);
while(!q.empty()){
int u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(auto vv:g[u]){
int v=vv.first,w=vv.second;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
q.emplace(dis[v],v);
}
}
}
}
signed main(){
cin.tie(nullptr)->sync_with_stdio(0);
cin>>h>>x>>y>>z;
--h;
for(int i=0;i<x;++i){
g[i].emplace_back((i+y)%x,y);
g[i].emplace_back((i+z)%x,z);
}
dijkstra(0);
int ans=0;
for(int i=0;i<x;++i)
if(h>=dis[i])
ans+=(h-dis[i])/x+1;
cout<<ans<<'\n';
return 0;
}
P2371 [国家集训队] 墨墨的等式
不过是上一道题的拓展。将值域从单一的 \([0,h-1]\) 变成了 \([l,r]\),同时由三个数变成了 \(n\) 个数。
统计是同理的,加上 \([0,r]\) 的答案再减去 \([0,l-1]\) 的答案即可。
signed main(){
cin.tie(nullptr)->sync_with_stdio(0);
cin>>n>>l>>r>>x;
--l;
for(int i=2;i<=n;++i){
cin>>y;
for(int j=0;j<x;++j) g[j].emplace_back((j+y)%x,y);
}
dijkstra(0);
int ans=0;
for(int i=0;i<x;++i){
if(r>=dis[i]) ans+=(r-dis[i])/x+1;
if(l>=dis[i]) ans-=(l-dis[i])/x+1;
}
cout<<ans<<'\n';
return 0;
}
AT_arc084_b [ABC077D] Small Multiple
注意到任何一个正整数都可以从 \(1\) 开始,按照某种顺序执行 \(\times10\) 和 \(+1\) 操作得到。最后执行的 \(+1\) 操作个数就是这个数的数位和。往最短路方面考虑。
建边方式为:对于所有 \(0\le k<n\),从 \(k\) 向 \(10k\bmod n\) 连边权为 \(0\) 的边,从 \(k\) 向 \((k+1)\bmod n\) 连边权为 \(1\) 的边。与一般的最短路不同的是,初始令 \(\text{dis}_1=1\),最后求的是 \(\text{dis}_0\) 的值。用 01BFS 的技巧可以做到 \(O(n)\) 复杂度。
#include<bits/stdc++.h>
using namespace std;
constexpr int MAXN=1e5+5;
vector<pair<int,int>>g[MAXN];
int dis[MAXN];
bool vis[MAXN];
void dijkstra(int s){
memset(dis,0x3f,sizeof(dis));
dis[s]=1;
priority_queue<pair<int,int>>q;
q.emplace(1,s);
while(!q.empty()){
int u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(auto vv:g[u]){
int v=vv.first,w=vv.second;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
q.emplace(-dis[v],v);
}
}
}
}
int main(){
int n;
cin>>n;
for(int k=0;k<n;++k){
g[k].emplace_back((k*10)%n,0);
g[k].emplace_back((k+1)%n,1);
}
dijkstra(1);
cout<<dis[0]<<'\n';
return 0;
}

浙公网安备 33010602011771号