同余最短路

同余最短路

什么神仙算法

这类问题的关键在于建模,头大了,主要总结几个题的思路技巧吧。

一般问题1:给定 n 个正整数,求这 n 个正整数能凑出多少其他的正整数(可以重复取)。

一般问题2:给定 n 个正整数,求这 n 个正整数不能拼出的最(大/小)的正整数。

(标题有 Link)

P3403 跳楼机

一般问题1

明显的楼层数只能在 [1,k] 之间,因为可以回到第一层,那么问题转化一下:

满足 ax+by+czi(modk)i 有几个。

对于每一个 i ,我们可以把它表示为 i=xn+mm 为余数,可以用 y,z 去凑出 m 这个余数,任何可以在 [0,x1] 范围内统计余数就可以得出楼层数,设 dis[i] 表示用若干个 y,z 能组成的余数(或者说能到达的位置,余数 (0,x1]),对于余数 m ,通过加一个 y 能到达余数 j ,则一定满足 (m+y)modx=j,可以这么说,余数从 ij 需要走的路径长度为 y

所以这样连边:

Add_edge(m,(m+y)moda,y)

Add_edge(m,(m+z)moda,z)

跑一遍最短路,得到了 [0,x) 范围内的能组成的余数 dis[i],i[0,x)

因为 x 可能比 k 大,所以只选取余数比 k 小的部分,看 kdis[i] 中有几个 a 统计贡献即可,+1 是为了加上 dis 本身这个位置。

Ans=m=0a1(hdisma+1)

/*
Knowledge : Rubbish Algorithm
Work by :Gym_nastics
Time : O(AC)
*/
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int INF=0x3f3f3f3f;
const int N=1e6+6;

int read() {
    int x=0,f=0;char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) f|=(ch=='-');
    for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch&15);
    return f?-x:x;
}

void print(int x) {
    if(x<0) putchar('-'),x=-x;
    if(x>9) print(x/10);
    putchar(x%10+48);
}

bool vis[N];
int dis[N],head[N],cnt;struct node{int v,w,nxt;}e[N];
void Add_edge(int u,int v,int w){e[++cnt]=(node){v,w,head[u]};head[u]=cnt;}

int h,a,b,c;

void SPFA(){
    queue<int>q;memset(dis,INF,sizeof dis);
    dis[1]=1;q.push(1);vis[1]=true;
    while(!q.empty()){
        int u=q.front();q.pop();vis[u]=false;
        for(int i=head[u];i;i=e[i].nxt){int v=e[i].v;
            if(dis[v]>dis[u]+e[i].w){
                dis[v]=dis[u]+e[i].w;
                if(!vis[v]) q.push(v),vis[v]=true;
            } 
        }
    }
}

signed main() {
   h=read();a=read();b=read();c=read();if(a==1 or b==1 or c==1) return print(h),0;
   for(int i=0;i<a;i++)Add_edge(i,(i+b)%a,b),Add_edge(i,(i+c)%a,c);
   SPFA();int Ans=0; 
   for(int i=0;i<a;i++)if(h>=dis[i]) Ans+=(((h-dis[i])/a)+1);
   return print(Ans),0;
}

AT3621 [ARC084B] Small Multiple

不知道的问题几

是个人都知道暴力不可过……

需要知道一点的是:一个数通过不停 +1×10 能构成任何数,并且 +1 对和的影响是 0×10 的影响是 0,设 dis[i] 表示 xmodk=i 的最小数字和,则 dis[0] 表示整除的情况。那就比较简单了。

Add_edge(i,i+1,1)

Add_edge(i,(i×10)modk,0)

注意 1 的数字和是 1,跑一遍最短路。

/*
Knowledge : Rubbish Algorithm
Work by :Gym_nastics
Time : O(AC)
*/
#include<bits/stdc++.h>
using namespace std;

const int INF=0x3f3f3f3f;
const int N=1e6+6;

int read() {
    int x=0,f=0;char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) f|=(ch=='-');
    for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch&15);
    return f?-x:x;
}

void print(int x) {
    if(x<0) putchar('-'),x=-x;
    if(x>9) print(x/10);
    putchar(x%10+48);
}

int head[N],cnt;struct node{int v,w,nxt;}e[N];
void Add_edge(int u,int v,int w){e[++cnt]=(node){v,w,head[u]};head[u]=cnt;}
int k;int dis[N];bool vis[N];

void SPFA(){
    memset(dis,INF,sizeof dis);
    dis[1]=1;vis[1]=1;queue<int>q;q.push(1);
    while(!q.empty()){
        int u=q.front();q.pop();vis[u]=0;
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].v;
            if(dis[v]>dis[u]+e[i].w){
                dis[v]=dis[u]+e[i].w;
                if(!vis[v]) vis[v]=1,q.push(v);
            }
        }
    }
}

signed main() {
   k=read();
   for(int i=1;i<k;i++) 
   Add_edge(i,(i+1)%k,1),Add_edge(i,(i*10)%k,0);
   SPFA();print(dis[0]);                                 
   return 0;
}

P2662 牛场围栏

一般问题2

我们把能取到的长度都存起来,去重排序,取其中最小的为 L

则最大取不到的数可以用 L 表示:Max=a×L+i,i[0,a)

dis[i] 表示组成长度模 Li 的最小长度, 肯定的,如果 dis[i]!=INF ,说明这个余数 i 能取到的,并且所需最短长度就是 dis[i] , 那答案就是 Ans=max{dis[i]}L

/*
Knowledge : Rubbish Algorithm
Work by :Gym_nastics
Time : O(AC)
*/
#include<cmath>
#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

const int INF=0x3f3f3f3f;
const int Mod=1e9+7;
const int N=4e6+6;

int read() {
    int x=0,f=0;char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) f|=(ch=='-');
    for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch&15);
    return f?-x:x;
}

void print(int x) {
    if(x<0) putchar('-'),x=-x;
    if(x>9) print(x/10);
    putchar(x%10+48);
}

bool vis[N];
int n,m,L=INF,a[N],cnt,dis[N];

void SPFA(){
    memset(dis,INF,sizeof dis);
    memset(vis,0,sizeof vis);
    queue<int>q;q.push(0);
    dis[0]=0;vis[0]=1;
    while(!q.empty()){
        int u=q.front();q.pop();vis[u]=false;
        for(int i=1;i<=cnt;i++){
            int v=(a[i]+u)%L;
            if(dis[v]>dis[u]+a[i]){
                dis[v]=dis[u]+a[i];
                if(!vis[v]) vis[v]=1,q.push(v);
            }
        }
    }
}

signed main() {
   n=read();m=read();
   for(int i=1,u;i<=n;i++){u=read();
   for(int j=0;j<=m;j++) 
   if(u-j>0&&(!vis[u-j])) 
   vis[u-j]=true,a[++cnt]=u-j,
   L=min(L,u-j);}
   if(vis[1]) return puts("-1"),0;
   SPFA();int Ans=0;
   for(int i=1;i<L;i++) Ans=max(Ans,dis[i]);
   print(Ans==INF?-1:Ans-L);
   return 0;
}

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

一般问题1

乍一看,我焯,国集,喝口水冷静一下,嗯,和跳楼机一模一样,只不过跳楼机只有 x,y,z 这个是多个,相同的做法。愉快切题。

/*
Knowledge : Rubbish Algorithm
Work by :Gym_nastics
Time : O(AC)
*/
#include<cmath>
#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;

const int INF=0x3f3f3f3f;
const int Mod=1e9+7;
const int N=4e7+6;

int read() {
    int x=0,f=0;char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) f|=(ch=='-');
    for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch&15);
    return f?-x:x;
}

void print(int x) {
    if(x<0) putchar('-'),x=-x;
    if(x>9) print(x/10);
    putchar(x%10+48);
}

int n,l,r,a[N],cnt,Min=INF;

int head[N],Cnt;struct node{int v,w,nxt;}e[N];
void Add_edge(int u,int v,int w){e[++Cnt]=(node){v,w,head[u]};head[u]=Cnt;}

int dis[N];bool vis[N];
void SPFA(){
    memset(dis,INF,sizeof dis);dis[0]=0;vis[0]=1;
    queue<int>q;q.push(0);
    while(!q.empty()){
        int u=q.front();q.pop();vis[u]=0;
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].v;
            if(dis[v]>dis[u]+e[i].w){
                dis[v]=dis[u]+e[i].w;
                if(!vis[v]) vis[v]=1,q.push(v);
            }
        }
    }
} 

int Query(int x){
    int res=0;
    for(int i=0;i<Min;i++) if(dis[i]<=x) res+=(x-dis[i])/Min+1;
    return res;
}

signed main() {
   n=read();l=read();r=read();
   for(int i=1,x;i<=n;i++){
       x=read();
       if(x) a[++cnt]=x,Min=min(Min,x);
   }
   for(int i=0;i<Min;i++)
   for(int j=1;j<=cnt;j++)
   if(a[j]^Min) Add_edge(i,(i+a[j])%Min,a[j]);   
   SPFA();print(Query(r)-Query(l-1));
   return 0;
}

本文作者:Gym_nastics

本文链接:https://www.cnblogs.com/BlackDan/p/16120952.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Gym_nastics  阅读(44)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起