先分享一些讲同余最短路的博客:
【算法笔记】一步一步推出来的同余最短路优化思路(千字长文,超详细)
P2662 牛场围栏(同余最短路)
同余最短路小结
虽然解题的算法是最短路,但思维方式还是动态规划,前提知识:(队列优化的)dij算法、spfa、动态规划入门
问题形式
1、给出n个整数,问用这n个整数可以拼出多少整数
2、给出n个整数,问最小不能拼出的整数是多少
3、给出n个整数,问最大不能拼出的整数是多少
套路是:物品无限+物品体积小+背包非常大=最短路
所以思路完善是:暴搜 ——> 完全背包 (dp) ——> 最短路(Dijkstra或spfa)
基本模型:动态规划
转移方程: f[(i + y) % mod] = f[i] + y
以此映射到最短路的dis[v] = dis[u] + val中
一些我自己帮助理解这个概念的辅助理解:
q1:为什么以最小的数值为基底?
以遍历 基底 以内所有为基础,基底越小越好,实际上,复杂度也在这个方法上降为 基底 的数据范围。
q2:为什么遍历各个点时,取的条件为(h>=d[i])(h为最高高度)?
问题是凑数,所以没有相减。到同余 i 时,它是从 d[i] 这个位置减去 k * x(x为基底,k为正整数)到 小于x数的以内的。
如果h < d[i],表示 i 不能由 a * y + b * z 表示,虽然 i == A * y + B * z - k * x ,但它不能在最高高度内表示。
当然有些 d[i] == inf ,表示这个点完全不能到达,通常这几个数的gcd不等于1会出现这种情况。
q3:最小/大不能拼出的整数怎么得到?
假如基底为t,最高高度为h,且各个整数最后的gcd为1。
最大的:把所有的d[i]-t求出来,取其中最大值。
for(int i=0;i<t;i++) sum=max(sum,d[i]-t);
最小的:把所有最小的d[i]-k*t(k为正整数,且这个式子的值大于0)求出来,取其中最小值。
sum=inf;
for(int i=0;i<t;i++) sum=min(sum,d[i]-(d[i]/t)*t);
具体问题具体分析。
跳楼机
我的部分代码:
const int maxn=2e5+7;
ll h,x,y,z,sum,d[maxn],head[maxn],cnt;
bool vis[maxn];
struct edge{ ll to,w,next; }e[maxn*5];
void add(ll u,ll v,ll w){
e[++cnt].w=w; e[cnt].to=v;
e[cnt].next=head[u]; head[u]=cnt;
}
void spfa(){
memset(d,inf,sizeof(d)); memset(vis,0,sizeof(vis));
d[1]=1; vis[1]=1;
queue<ll>q; q.push(1);
while(!q.empty()){
ll u=q.front(); q.pop(); vis[u]=0;
for(ll i=head[u];i;i=e[i].next){
int v=e[i].to,w=e[i].w;
if(d[v]>d[u]+w){
d[v]=d[u]+w;
if(!vis[v]){ q.push(v); vis[v]=1; }
}
}
}
}
int main(){
h=read(); x=read(); y=read(); z=read();
if(x==1||y==1||z==1){
printf("%lld",h);
return 0;
}
for(int i=0;i<x;i++){
add(i,(i+y)%x,y);
add(i,(i+z)%x,z);
}
spfa();
for(int i=0;i<x;i++) if(h>=d[i]) sum+=(h-d[i])/x+1;
printf("%lld",sum);
}
牛场围栏
不存在的情况有两种,其中不存在这个最大值时,你如果跑d[]数值,遇到d[i]>=inf也不是不可以,但其实这种做法大概相当于猜测利用了数据比较小,最好用gcd判断。
求不能凑出的最大数的时候,我们要先考虑d数组的意义。d[i]即为其他数字能凑出来的%t=i的最小数字,那么d[i]+t,d[i]+2t,d[i]+3t… d[i]以上跳所有个t都能达到。
那么%t=i的数字,最大凑不出来的就是d[i]-t。(1、前面验证a[0]-m>1 -------2、d[i]-k*t 也会凑不出(其中k为正整数))
那么很显然了,把所有的d[i]-t求出来,取其中最大值。
const int maxn=5e6+7;
int gcd(int a,int b){ return b?gcd(b,a%b):a; }
int n,m,a[3000],head[3000],cnt,d[3000],t,sum;
bool vis[3000];
struct edge{ int to,w,next; }e[maxn];
void add(int u,int v,int w){
e[++cnt].w=w; e[cnt].to=v;
e[cnt].next=head[u]; head[u]=cnt;
}
void spfa(){
memset(d,inf,sizeof(d)); memset(vis,0,sizeof(vis));
d[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].next){
int v=e[i].to,w=e[i].w;
if(d[v]>d[u]+w){
d[v]=d[u]+w;
if(!vis[v]){ q.push(v); vis[v]=1; }
}
}
}
}
int main(){
n=read(); m=read();
for(int i=0;i<n;i++){
a[i]=read();
if(!i) t=a[i];
else t=gcd(a[i],t);
}
sort(a,a+n);
if(a[0]-m<=1||t!=1){
cout<<-1;
return 0;
}
t=a[0]-m;
for(int i=0;i<n;i++) for(int j=0;j<t;j++) for(int k=a[i]-m;k<=a[i];k++) add(j,(j+k)%t,k);
spfa();
for(int i=0;i<t;i++) sum=max(sum,d[i]-t);
cout<<sum;
}
[国家集训队]墨墨的等式
看数据比较大,用了队列优化的dij算法代替spfa算法(spfa我也试了,会超时)。
const int maxn=5e5+7;
ll n,l,r,t,a[20],head[maxn],d[maxn],sum,cnt;
struct edge{ ll to,w,next; }e[maxn*12];
void add(ll u,ll v,ll w){
e[++cnt].w=w; e[cnt].to=v;
e[cnt].next=head[u]; head[u]=cnt;
}
struct node{
int x; ll dis;
node(int xx=0,ll dd=0):x(xx),dis(dd){}
bool operator <(const node &b)const{ return dis>b.dis; }
};
void dij(){
memset(d,inf,sizeof(d)); d[0]=0;
priority_queue<node>q; q.push(node(0,0));
while(!q.empty()){
int u=q.top().x; ll temp=q.top().dis; q.pop();
if(temp!=d[u]) continue;
for(ll i=head[u];i;i=e[i].next){
ll v=e[i].to,w=e[i].w;
if(d[v]>d[u]+w){
d[v]=d[u]+w;
q.push(node(v,d[v]));
}
}
}
}
int main(){
n=read(); l=read(); r=read();
for(int i=0;i<n;i++) a[i]=read();
sort(a,a+n); t=a[0];
if(t==1){
cout<<r-l+1<<endl;
return 0;
}
for(int i=0;i<n;i++) for(int j=0;j<t;j++) add(j,(j+a[i])%t,a[i]);
dij(); ll x=0,y=0;
for(int i=0;i<t;i++){
if(d[i]<=r) x+=(r-d[i])/t+1;
if(d[i]<l) y+=(l-1-d[i])/t+1;
}
sum=x-y;
printf("%lld",sum);
}