D122【模板】同余最短路 Dijkstra 算法 P3403 跳楼机

D122【模板】同余最短路 Dijkstra 算法 P3403 跳楼机_哔哩哔哩_bilibili

 

P3403 跳楼机 - 洛谷 

给定 $x,y,z,h$,求出有多少 $k\in [1,h]$ 可以使等式 $ax+by+cz=k$ 存在非负整数解。

同余最短路问题形式:

1. 给定 n 个整数,求这 n 个整数能拼凑出多少的其他整数(n 个整数可以重复取)

2. 给定 n 个整数,求这 n 个整数不能拼凑出的最小(最大)的整数

3. 至少要拼几次才能拼出模 K 余 p 的数

 

例:$h=16,x,y,z=4,7,9$. 先把楼层总数和起点楼层向下偏移一层 $h=15$,从 0 层出发,可达楼层如图

可达楼层看做节点,$x,y,z$ 看做边权. 发现节点可以按模 $4$ 的余数分类:$\{4,8,12\},\{9,13\},\{14\},\{7,11,15\}$

并且同类节点到 0 点的最短路相同:$d[0]=0,\;d[1]=9,\;d[2]=14,\;d[3]=7$

同余最短路利用同余来构造一些状态(以便优化空间复杂度),把这些余数状态看作单源最短路中的点

连边:$i\overset{y}{\rightarrow} (i+y)\%x$, $add(i,(i+y)\%x,y)$   例如 $add(3,(3+7)\%4,7)$,即 $add(3,2,7)$

状态转移:$d[(i+y)\% x]=d[i]+y$,其中 $d[i]$ 表示模 $x$ 的余数为 $i$ 时能到达的最小楼层

统计节点数:余数 0 的个数 $=\lfloor\frac{H}{4}\rfloor+1$,余数 3 的个数 $=\lfloor\frac{H-d[3]}{4}\rfloor+1$,所以 $ans=\sum (\lfloor\frac{H-d[i]}{x}\rfloor+1)$ 

其实,$x$ 不一定非要取三个数中最小的,任取一个做模都可以. 不过取最小的,状态点数会少,更好来

 

// 同余最短路 Dijkstra 算法 O(MlogN)
#include<bits/stdc++.h>
#define ll long long
#define pli pair<ll,int>
using namespace std;

const int N=100010,M=N*2;
const ll inf=(1ull<<63)-1; //long long 的最大值
ll h[M],idx,to[M],ne[M],ww[M];
void add(ll u,ll v,ll w){
  to[++idx]=v;ww[idx]=w;ne[idx]=h[u];h[u]=idx;
}
ll H,x,y,z;
ll d[N],vis[N];

void dijkstra(){
  for(int i=0;i<x;i++) d[i]=inf; d[0]=0;
  priority_queue<pli,vector<pli>,greater<pli> > q; 
  q.push({0,0});
  
  while(!q.empty()){
    int u=q.top().second; q.pop();
    if(vis[u]) continue; vis[u]=1;
    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});
      }
    }
  }
}
int main(){
  cin>>H>>x>>y>>z;
  for(int i=0; i<x; i++){
    add(i,(i+y)%x,y);
    add(i,(i+z)%x,z);
  }
  
  dijkstra();
  ll ans=0; --H;
  for(int i=0;i<x;i++)
    if(H>=d[i]) ans+=(H-d[i])/x+1;
  cout<<ans<<'\n';
}

 

P2371 [国家集训队] 墨墨的等式 - 洛谷

给定 $n,a_{1…n​},l,r$,求出有多少 $b∈[l,r]$ 可以使等式 $\sum_{i=1}^n a_ix_i=b$ 存在非负整数解。

思路

题目用 $n$ 个已知整数 $a_i(0\le a_i \le 5\times 10^5)$ 拼凑 $[l,r]$ 区间内的数,能拼出几个

拼凑区间内的数,用前缀和思想,拼凑 $[0,r]$ 内的个数 减去 拼凑 $[0,l-1]$ 内的个数

用上一题的同余最短路求解,注意本题的起点就是 0,从 0 点出发建图,$r,l$ 不用向下偏移 $1$

 

// 同余最短路 Dijkstra 算法 O(MlogN)
#include<bits/stdc++.h>
#define ll long long
#define pii pair<ll,int>
using namespace std;

const int N=5e5+5,M=N*12;
const ll inf=1e18;
ll h[N],idx,to[M],ne[M],ww[M];
void add(ll u,ll v,ll w){
  to[++idx]=v;ww[idx]=w;ne[idx]=h[u];h[u]=idx;
}
ll n,m,l,r,x,a[N];
ll d[N],vis[N];

void dijkstra(){
  for(int i=0;i<x;++i)d[i]=inf; d[0]=0;
  priority_queue<pii,vector<pii>,greater<pii> > q; 
  q.push({0,0});
  while(!q.empty()){
    int u=q.top().second; q.pop();
    if(vis[u]) continue; vis[u]=1;
    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});
      }
    }
  }
}
int main(){
  scanf("%lld%lld%lld",&n,&l,&r);
  for(int i=1;i<=n;++i){
    scanf("%lld",&x);
    if(x) a[++m]=x; //x=0无用,去掉
  }
  sort(a+1,a+m+1); x=a[1]; //a1做模,状态少
  for(int i=0;i<x;++i)for(int j=2;j<=m;++j)
    add(i,(i+a[j])%x,a[j]);
  
  dijkstra();
  ll ans=0; --l;
  for(int i=0;i<x;++i){
    if(r>=d[i]) ans+=(r-d[i])/x+1;
    if(l>=d[i]) ans-=(l-d[i])/x+1;
  }
  printf("%lld\n",ans);
}

 

posted @ 2026-03-30 20:29  董晓  阅读(170)  评论(0)    收藏  举报