【同余最短路】【例题集合】洛谷P3403 跳楼机/P2371 墨墨的等式

匿名 (未验证) 提交于 2019-12-02 23:42:01

接触到的新内容,【同余最短路】。

代码很好写,但思路不好理解。

同余最短路,并不是用同余来跑最短路,而是通过同余来构造某些状态,从而达到优化时间空间复杂度的目的。往往这些状态就是最短路中的点,可以类比差分约束跑最短路(f[i]+w<=f[j]构造最短路不等式(例题luogu 小k的农场))

――来自洛谷P3403 Liao_rl 的题解

【P3403跳楼机】

题目背景

DJL为了避免成为一只咸鱼,来找srwudi学习压代码的技巧。

题目描述

经过改造,srwudi的跳楼机可以采用以下四种方式移动:

  1. 向上移动x层;

  2. 向上移动y层;

  3. 向上移动z层;

  4. 回到第一层。

一个月黑风高的大中午,DJL来到了srwudi的家,现在他在srwudi家的第一层,碰巧跳楼机也在第一层。DJL想知道,他可以乘坐跳楼机前往的楼层数。

输入输出格式

输入格式:

第一行一个整数h,表示摩天大楼的层数。

第二行三个正整数,分别表示题目中的x, y, z。

输出格式:

一行一个整数,表示DJL可以到达的楼层数。

输入输出样例

输入样例#1:
15 4 7 9 
输出样例#1:
9
输入样例#2:
33333333333 99005 99002 100000 
输出样例#2:
33302114671 

说明

可以到达的楼层有:1,5,8,9,10,12,13,14,15

想不出来不要死磕这一题,先看看第三题。。。。

1<=h<=2^63-1

1<=x, y, z<=100000

˼·

首先考虑低一级的问题,假如只存在x,y两种操作而不是x,y,z三种(暂时忽略回到第一层)

那么很好想,只要用y能凑出来x的剩余系(%x的所有余数的集合),那么所有楼层都能达到

设i为%x的一个余数,f[i]为用y能凑出来的%x=i的最小高度,那么f[i]再往上跳任意个x的层数都能达到,即对于f[x]能达到的层数为(h-f[i])/x+1。因为程序里面的除法是向下取整,后面还要补一个1。另外,由于f数组的存在,一定不存在整除的情况。

那么把所有这样的f[i]加起来就是所求的答案。推广到三种操作的情况,只要用y和z去凑f就能得到答案。

接下来问题是怎么求f[i]。假设我们已经求出一个f[i],在它的基础上再执行一遍y或z操作,例如用f[i]+y,显然能得到f[i+y]。那么考虑让f[i+y]和f[i]都成为点,用y作为边相连,以已知状态推出其它所求状态。因为f数组范围是x的剩余系,为了使f最小我们选择x,y,z中最小的成为x,连边时f[i+y]要作为f[(i+y)%x]这个点使用,对结果并无影响。

考虑起始状态是第一层且最低只能回到第一层,那么已知状态是f[1]=1。用SPFA来松弛这些等式关系,跑出f数组。最后用h计算总答案的时候,要排除掉f[i]中>h的答案。

这道题中的同余最短路思想,个人理解在于利用同余来构造x的剩余系这些点。f[i],f[i]+x,f[i]+2x,...,f[i]+kx,这些楼层数都%x同余,那么只要建立f[i]的状态就能用极低的时间复杂度求出f[i]+x,f[i]+2x等是否可行。

代码:

#include<iostream> #include<cstdio> #include<cstring> #include<queue> using namespace std; long long h,ans,f[100011]; long long x,y,z,vis[100011]; queue<long long>q; long long ver[200011],head[100011],edge[200011],Next[200011],tot; void add(long long x,long long y,long long z){     ver[++tot]=y;     Next[tot]=head[x];     edge[tot]=z;     head[x]=tot; } int main() { //    freopen("srwudi2.in","r",stdin); //    freopen("srwudi2.out","w",stdout);     scanf("%lld%lld%lld%lld",&h,&x,&y,&z);     if(x==1||y==1||z==1){         printf("%lld",h);         return 0;     }     memset(f,0x7f,sizeof(f));     f[1]=1;     vis[1]=1;     if(y<z)swap(y,z);     if(x<z)swap(x,z);     for(int i=0;i<z;i++)add(i,(i+x)%z,x),add(i,(i+y)%z,y);     q.push(1);     while(!q.empty()){         long long u=q.front();         q.pop();         vis[u]=0;         for(int i=head[u];i;i=Next[i]){             long long v=ver[i],zz=edge[i];             if(f[v]>f[u]+zz){                 f[v]=f[u]+zz;                 if(!vis[v]){                     vis[v]=1;                     q.push(v);                 }             }         }     }     for(int i=0;i<z;i++){         if(f[i]<=h)ans+=(h-f[i])/z+1;     }     printf("%lld",ans); }

那么再来看一道相似的题

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

题目描述

墨墨突然对等式很感兴趣,他正在研究a_1x_1+a_2x_2+…+a_nx_n=Ba1x1+a2x2++anxn=B存在非负整数解的条件,他要求你编写一个程序,给定N、{an}、以及B的取值范围,求出有多少B可以使等式存在非负整数解。

输入输出格式

输入格式:

输入的第一行包含3个正整数,分别表示NN、B_{Min}BMinB_{Max}BMax分别表示数列的长度、B的下界、B的上界。

输入的第二行包含N个整数,即数列{an}的值。

输出格式:

输出一个整数,表示有多少b可以使等式存在非负整数解。

输入输出样例

输入样例#1:
2 5 10 3 5
输出样例#1:
5

说明

对于20%的数据,N \le 5N5,1 \le B_{Min} \le B_{Max} \le 101BMinBMax10。

对于40%的数据,N \le 10N10,1 \le B_{Min} \le B_{Max} \le 10^61BMinBMax106。

对于100%的数据,N \le 12N12,0 \le a_i \le 5*10^50ai5105,1 \le B_{Min} \le B_{Max} \le 10^{12}1BMinBMax1012。

˼·

这道题一眼看上去就和上面那道很像对吧XD

但是这道题不仅限于x,y,z而是拓展到了n个数字。不过这并不影响,可以如法炮制,把最小的x选出来,构造它的剩余系对应状态,只是要建很多点了而已。

还有一个不同的地方是这题的询问是有范围的,而不是1-h,所以在询问的时候可以利用很多题目里常见的前缀和思想,即query(max)-query(min-1),来得出答案。

代码:

#include<iostream>  #include<cstdio> #include<cstring> #include<queue> using namespace std; long long n,a[13],x=10000000,vis[500101]; long long f[500101]; long long tot,ver[6000101],Next[6000101],head[500101],edge[6000101]; long long maxx,minn,ans; queue<long long>q; void add(long long x,long long y,long long z){     ver[++tot]=y;     Next[tot]=head[x];     edge[tot]=z;     head[x]=tot; } int main() {     scanf("%lld%lld%lld",&n,&minn,&maxx);     memset(f,0x7f,sizeof(f));     for(int i=1;i<=n;i++){         scanf("%lld",&a[i]);         if(a[i]==1){             printf("%lld",maxx-minn+1);             return 0;         }         x=min(a[i],x);     }     for(int i=1;i<=n;i++){         if(a[i]==x)continue;         for(int j=0;j<x;j++){             add(j,(j+a[i])%x,a[i]);         }     }     f[0]=0,vis[0]=1;     q.push(0);     while(!q.empty()){         int u=q.front();         q.pop();         vis[u]=0;         for(int i=head[u];i;i=Next[i]){             long long v=ver[i],z=edge[i];             if(f[v]>f[u]+z){                 f[v]=f[u]+z;                 if(!vis[v]){                     vis[v]=1;                     q.push(v);                 }             }         }     }     minn-=1;     for(int i=0;i<x;i++){         if(f[i]<=maxx){             ans+=(maxx-f[i])/x+1;         }         if(f[i]<=minn){             ans-=((minn-f[i])/x+1);         }     }     printf("%lld",ans);     return 0; }

(来自一个WA多了气急败坏的无脑long long选手)

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!