同余最短路在我們做題中很少出現,是屬於比較冷門的一種算法。當題目中出現例如“給定m個整數,求這m個整數能拼湊出多少的其他整數(這m個整數可以重復取)”,以及“給定m個整數,求這m個整數不能拼湊出的最小(最大)的整數”的話時我們可以考慮同余最短路的方法。
例1:P3403 跳樓機
-
我們考慮操作2和操作3 \(mod\) \(x\)能到達的樓層,可以發現最終答案一定落在\(x\)的剩余系內。當我們知道這些合法的剩余系的時候,就可以不斷累加\(x\)的值直到最大高度來求出可以到達的不同高度。
-
如何求出只用操作2,3在每個剩余系到達的最小高度?設\(g(k)\)表示在\(x\)的剩余系內到達的樓層,如果此時加上操作2,3那么可以得出:
-
那么怎么求出由底層(k=0)到其他剩余系中元素的最小代價呢?最短路可以方便地解決這個問題[1]:構建始點為\(k\),終點為\((k+y)modx\)且長度為\(y\)的邊,最后從節點0跑最短路就可以了。對於操作3也是如此,同樣一起建邊。(初始化dist[0]=1)
-
現在我們已經求出\(dist[]\)數組,內部存儲了僅使用操作2,3到該節點需要的最小代價(即到達的最小樓層)。最后需要求出用操作1能到達的樓層數,掃描\(x\)中的剩余系,代入公式:\(ans+=(h-dist[i])/x+1\)就能得出最終答案。
示意圖:
Code:
#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define N 1000010
using namespace std;
ll first[N],next[N],go[N],cost[N],tot;
ll dist[N],vis[N],h,x,y,z,ans;
inline void add_edge(ll u,ll v,ll w){
next[++tot]=first[u];
first[u]=tot;
go[tot]=v;
cost[tot]=w;
}
inline void spfa(){
queue<ll>q;
memset(dist,INF,sizeof(dist));
memset(vis,0,sizeof(vis));
dist[0]=1;vis[0]=1;
q.push(0);
while(!q.empty()){
ll u=q.front();q.pop();
vis[u]=0;
for(ll e=first[u];e;e=next[e]){
ll v=go[e],w=cost[e];
if(dist[v]>dist[u]+w){
dist[v]=dist[u]+w;
if(!vis[v]){
q.push(v);vis[v]=1;
}
}
}
}
}
signed main()
{
scanf("%lld%lld%lld%lld",&h,&x,&y,&z);
if(x==1||y==1||z==1){printf("%d",h);return 0;}//可以直接到任何樓層
for(int i=0;i<=x-1;i++){
add_edge(i,(i+y)%x,y);
add_edge(i,(i+z)%x,z);
}
spfa();
for(int i=0;i<=x-1;i++){
if(dist[i]<=h) ans+=(h-dist[i])/x+1;//統計答案
}
printf("%lld",ans);
return 0;
}
例2:P2662 牛場圍欄
-
這道題同樣可以用同余最短路解決,
區別是上一道題是紫題,這道題是藍題(逃。題目要求求出最大不能拼湊出來的木板長度,因此我們把最短的木板作為剩余系,掃描其他的木板並建邊。題目另外說每個木板可以最多截掉m米,那么只要再掃描到每個木板的時候依次掃描這個木板能被截成的長度就好了。 -
如何解決不能湊出來的最大木板長度?我們求出的dist[]數組是不使用(a[1]-m)的長度外能湊出來的最小長度,那么只要從dist[]中減去這個值就能得出剩余系中這類不能拼湊的最大值。對所有類都進行這樣的處理,最終就能得出不能湊出的最大值。
-
此外題目特判任意長度都可以拼成或最大值不存在輸出"-1"。先考慮任意長度都能拼成,如果這個木板的長度能被削成1,那么一定可以達到任意長度,對應為
if(a[1]-m<=1)
[2]。最大值不存在的情況為剩余系中一類數都無法拼成,即dist[k]沒有被松弛。
具體細節詳見代碼:
#include<bits/stdc++.h>
#define N 2500010
#define INF 0x3f3f3f3f
using namespace std;
int n,m,ans,a[500],dist[N<<1],vis[N<<1],tot;
int first[N],next[N<<1],go[N<<1],cost[N<<1];
inline void add_edge(int u,int v,int w){
next[++tot]=first[u];
first[u]=tot;
go[tot]=v;
cost[tot]=w;
}
inline void spfa(){
queue<int> q;
memset(dist,INF,sizeof(dist));
memset(vis,0,sizeof(vis));
dist[0]=0;vis[0]=1;
q.push(0);
while(!q.empty()){
int u=q.front();q.pop();
vis[u]=0;
for(int e=first[u];e;e=next[e]){
int v=go[e],w=cost[e];
if(dist[v]>dist[u]+w){
dist[v]=dist[u]+w;
if(!vis[v]){
q.push(v);vis[v]=1;
}
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);//木棒長度
sort(a+1,a+n+1);
if(a[1]-m<=1){printf("-1");return 0;}//任意長度都能達到
for(int i=1;i<=n;i++)//掃描每根木棒
for(int j=max(a[i]-m,a[i-1]+1);j<=a[i];j++)//掃描木棒能削出來的長度,其中max()保證不重復
for(int k=0;k<a[1]-m;k++) add_edge(k,(k+j)%(a[1]-m),j);//0~a[1]-1 作為剩余系,操作同例1
spfa();
for(int i=1;i<=a[1]-m-1;i++){
if(dist[i]>100000000){//最大值不存在,mod x=i這一類數全湊不出來
printf("-1");return 0;
}
ans=max(ans,dist[i]-(a[1]-m));
}
printf("%d",ans);
return 0;
}