同余最短路
同余最短路
什么神仙算法
这类问题的关键在于建模,头大了,主要总结几个题的思路技巧吧。
一般问题1:给定 \(n\) 个正整数,求这 \(n\) 个正整数能凑出多少其他的正整数(可以重复取)。
一般问题2:给定 \(n\) 个正整数,求这 \(n\) 个正整数不能拼出的最(大/小)的正整数。
(标题有 Link)
P3403 跳楼机
一般问题1
明显的楼层数只能在 \([1,k]\) 之间,因为可以回到第一层,那么问题转化一下:
满足 \(ax+by+cz \equiv i \pmod k\) 的 \(i\) 有几个。
对于每一个 \(i\) ,我们可以把它表示为 \(i=xn+m\),\(m\) 为余数,可以用 \(y,z\) 去凑出 \(m\) 这个余数,任何可以在 \([0,x-1]\) 范围内统计余数就可以得出楼层数,设 \(dis[i]\) 表示用若干个 \(y,z\) 能组成的余数(或者说能到达的位置,余数 \(\in (0,x-1]\)),对于余数 \(m\) ,通过加一个 \(y\) 能到达余数 \(j\) ,则一定满足 \((m+y) \bmod x=j\),可以这么说,余数从 \(i\) 到 \(j\) 需要走的路径长度为 \(y\)。
所以这样连边:
跑一遍最短路,得到了 \([0,x)\) 范围内的能组成的余数 \(dis[i],i\in[0,x)\)
因为 \(x\) 可能比 \(k\) 大,所以只选取余数比 \(k\) 小的部分,看 \(k-dis[i]\) 中有几个 \(a\) 统计贡献即可,\(+1\) 是为了加上 \(dis\) 本身这个位置。
/*
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\) 和 \(\times 10\) 能构成任何数,并且 \(+1\) 对和的影响是 \(0\),\(\times 10\) 的影响是 \(0\),设 \(dis[i]\) 表示 \(x \bmod k=i\) 的最小数字和,则 \(dis[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 \times L+i,i\in[0,a)\)
设 \(dis[i]\) 表示组成长度模 \(L\) 为 \(i\) 的最小长度, 肯定的,如果 \(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;
}