同余最短路

87 篇文章 0 订阅
文章介绍了如何将同余类视为图中的节点,并通过建立边来解决在模意义下寻找最短路径的问题。具体涉及模X的同余类计算,利用Dijkstra算法求解最小成本路径,并给出相关代码示例。这些方法常见于编程竞赛中的数学和图论题目。
摘要由CSDN通过智能技术生成

同余最短路就是把每一个同余类当成一个结点,在同余类之间建边,然后跑最短路

答案统计的时候对每个同余类单独计算贡献

题意:

 

思路: 

答案可以对模X的所有同余类计算贡献

设dis[i]为在模X意义下,+Y和+Z之后%X余数为i的能凑出来的最小数

那么答案就是枚举同余类,对于每个同余类,能到达的楼层数的贡献就是(H-dis[i])/X+1

前者是从这个最小数开始+X,能到达的楼层数,+1是因为要算它本身

每个同余类都看作是一个结点,同余类之间建边

起点是1,因为一开始是在第一层,%X=1

Code:

#include <bits/stdc++.h>

#define int long long

using namespace std;

const int mxn=1e5+10;
const int mxe=1e5+10;
const int mod=1e9+7;

struct ty{
    int to,next,w;
}edge[mxe<<2];

struct ty2{
    int x,dis;
    bool operator<(const ty2&oth)const{
        return oth.dis<dis;
    }
};

priority_queue<ty2> Q;

int H,X,Y,Z,tot=0;
int head[mxn];
int dis[mxn],vis[mxn];

void G_init(){
    tot=0;
    memset(head,-1,sizeof(head));
}
void add(int u,int v,int w){
    edge[tot].w=w;
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
void dij(){
    memset(dis,0x3f3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[1]=1;
    Q.push({1,1});
    while(!Q.empty()){
        auto u=Q.top();
        Q.pop();
        if(vis[u.x]) continue;
        vis[u.x]=1;
        for(int i=head[u.x];~i;i=edge[i].next){
            if(dis[edge[i].to]>dis[u.x]+edge[i].w){
                dis[edge[i].to]=dis[u.x]+edge[i].w;
                Q.push({edge[i].to,dis[edge[i].to]});
            }
        }
    }
}
void solve(){
    cin>>H>>X>>Y>>Z;
    if(X==1||Y==1||Z==1){
        cout<<H<<'\n';
        return;
    }
    G_init();
    for(int i=0;i<X;i++){
        add(i,(i+Y)%X,Y);
        add(i,(i+Z)%X,Z);
    }
    dij();
    int ans=0;
    for(int i=0;i<X;i++){
        if(dis[i]<=H) ans+=(H-dis[i])/X+1;
    }
    cout<<ans<<'\n';
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int __=1;//cin>>__;
    while(__--)solve();return 0;
}

AGC D

 

思路:

考虑模K意义下的所有整数

对于每个整数,都是由1经过*10和+1这两种操作转移过来的

因此同余类之间建两条边:*10和+1

最后的数位和就是+1操作的次数,*10对数位和没有贡献

起点是1,终点是0 

Code:

#include <bits/stdc++.h>

#define int long long

using namespace std;

const int mxn=1e5+10;
const int mxe=1e5+10;
const int mod=1e9+7;

struct ty{
    int to,next,w;
}edge[mxe<<2];

struct ty2{
    int x,dis;
    bool operator<(const ty2&oth)const{
        return oth.dis<dis;
    }
};

priority_queue<ty2> Q;

int N,tot=0;
int head[mxn];
int dis[mxn],vis[mxn];

void G_init(){
    tot=0;
    memset(head,-1,sizeof(head));
}
void add(int u,int v,int w){
    edge[tot].w=w;
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
void dij(){
    memset(dis,0x3f3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[1]=1;
    Q.push({1,0});
    while(!Q.empty()){
        auto u=Q.top();
        Q.pop();
        if(vis[u.x]) continue;
        vis[u.x]=1;
        for(int i=head[u.x];~i;i=edge[i].next){
            if(dis[edge[i].to]>dis[u.x]+edge[i].w){
                dis[edge[i].to]=dis[u.x]+edge[i].w;
                Q.push({edge[i].to,dis[edge[i].to]});
            }
        }
    }
}
void solve(){
    cin>>N;
    G_init();
    for(int i=0;i<N;i++){
        add(i,(i*10)%N,0);
        add(i,(i+1)%N,1);
    }
    dij();
    cout<<dis[0]<<'\n';
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int __=1;//cin>>__;
    while(__--)solve();return 0;
}

题意:

思路: 

其实这类的板子题都一模一样,怪不得赛时这么多人过QwQ

设dis[i]为在模N意义下达到%N余数为i的最小代价

Code:

#include <bits/stdc++.h>
 
#define int long long
 
using namespace std;
 
const int mxn=1e5+10;
const int mxe=1e5+10;
const int mod=1e9+7;
const int Inf=1e18;
 
struct ty{
    int to,next,w;
}edge[mxe<<2];
 
struct ty2{
    int x,dis;
    bool operator<(const ty2&a)const{
        return a.dis<dis;
    }
};
 
priority_queue<ty2> Q;
 
int N,y,p,x,q,tot=0,s,t;
int c[mxn],head[mxn],dis[mxn],vis[mxn];
 
void add(int u,int v,int w){
    edge[tot].w=w;
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
void G_init(){
    tot=0;
    for(int i=0;i<=N+1;i++){
        head[i]=-1;
    }
}
void dij(){
    for(int i=0;i<=N;i++) dis[i]=Inf;
    memset(vis,0,sizeof(vis));
    Q.push({s,0});
    dis[s]=0;
    while(!Q.empty()){
        auto u=Q.top();
        Q.pop();
        if(vis[u.x]) continue;
        vis[u.x]=1;
        for(int i=head[u.x];~i;i=edge[i].next){
            if(vis[edge[i].to]) continue;
            if(dis[edge[i].to]>dis[u.x]+edge[i].w){
                dis[edge[i].to]=dis[u.x]+edge[i].w;
                Q.push({edge[i].to,dis[edge[i].to]});
            }
        }
    }
}
void solve(){
    cin>>N>>p>>x>>q>>y;
    G_init();
    int sum=0;
    for(int i=1;i<=N;i++){
        cin>>c[i];
        sum+=c[i];
        sum%=N;
    }
    for(int i=0;i<=N-1;i++){
        add(i,(i+x)%N,p);
        add(i,((i-y)%N+N)%N,q);
    }
    s=sum%N;
    t=0;
    dij();
    if(dis[t]==Inf) cout<<-1<<'\n';
    else cout<<dis[t]<<'\n';
}
signed main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int __=1;//cin>>__;
    while(__--)solve();return 0;
}

同余最短路是指在模一个数的意义下求解最短路的问题。具体来说,给定一个有向图 $G=(V,E)$,每个边 $e\in E$ 都有一个非负的权值 $w_e$,以及一个模数 $p$。同时给定一个起点 $s$ 和一个终点 $t$。求一条从 $s$ 到 $t$ 的路径,使得路径上所有边权的和在模 $p$ 意义下最小。 证明正确性: 同余最短路算法基于 Bellman-Ford 算法,可以证明其正确性。Bellman-Ford 算法通过松弛每条边 $e$,对于每个顶点 $v$,维护从起点 $s$ 到 $v$ 的最短距离 $d_v$。同余最短路算法也是基于松弛每条边,但是对于每个顶点 $v$,维护从起点 $s$ 到 $v$ 在模 $p$ 意义下的最短距离 $d_v$。 因为同余最短路算法是在模 $p$ 意义下计算距离,所以需要使用模运算。在松弛每条边时,需要更新到达每个顶点的最短距离。具体地,设 $v$ 是边 $e=(u,v)$ 的终点,$w_e$ 是边 $e$ 的边权,则松弛边 $e$ 的操作可以表示为: $$d_v\leftarrow\min(d_v,d_u+w_e\mod p)$$ 其中,$d_u$ 表示从起点 $s$ 到 $u$ 的最短距离。 同余最短路算法的正确性证明可以参考 Bellman-Ford 算法的正确性证明。同余最短路算法的时间复杂度为 $O(VE)$。 代码实现: 以下是同余最短路算法的 Python 代码实现: ```python from collections import deque def shortest_path_mod_p(graph, s, t, p): n = len(graph) INF = float('inf') dist = [INF] * n dist[s] = 0 q = deque([s]) in_queue = [False] * n in_queue[s] = True while q: u = q.popleft() in_queue[u] = False for v, w in graph[u]: d = (dist[u] + w) % p if dist[v] > d: dist[v] = d if not in_queue[v]: q.append(v) in_queue[v] = True return dist[t] ``` 其中,`graph` 是图的邻接表表示,每个元素是一个二元组 `(v, w)`,表示从节点 `u` 到节点 `v` 有一条边权为 `w` 的边。`s` 和 `t` 分别是起点和终点的编号,`p` 是模数。函数返回从起点 `s` 到终点 `t` 在模 `p` 意义下的最短距离。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值