同余最短路
同余最短路
判断形如
的方程是否有自然数解
算法流程
我们找到所有的\(a_i\)中的最小值作为模数,如果一个数\(p\)可以被构造,那么\(p+k\times a_1\)也就一定可以被构造
也就是对于每一个余数\(i\),我们需要找到最小的,\(p \bmod a_1=i\)的\(p\)
每加上一个数\(a_j\),我们可以实现\(p\bmod a_1=i\)到\(p\bmod a_1=(i+a_j)\bmod a_1\)的转化,所以连边\(i\to (i+a_j)\bmod a_1\),边权为\(a_j\)
跑Dijkstra即可
例题
P3403 跳楼机 模板题
P2371 [国家集训队]墨墨的等式 模板题
//P2371 [国家集训队]墨墨的等式
int a[14],n;
ll l,r;
const int N=5e5+5;
struct Edge{int to,next;ll len;}e[N<<4];
int head[N],ecnt;
inline int add(int a,int b,int mod){return (a+b)%mod;}
inline void adde(int u,int v,ll w){
e[++ecnt]=(Edge){v,head[u],w};head[u]=ecnt;
}
priority_queue<pair<ll,int> > q;
ll dis[N];bool vis[N];
void dijkstra(int s){
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
q.push(make_pair(0,s));
while(!q.empty()){
int u=q.top().second;
q.pop();
if(vis[u]==1)continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(dis[v]>dis[u]+e[i].len){
dis[v]=dis[u]+e[i].len;
if(!vis[v])
q.push(make_pair(-dis[v],v));
}
}
}
}
int main(){
scanf("%d %lld %lld",&n,&l,&r);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
if(!a[i])--i,--n;
}
sort(a+1,a+1+n);
for(int i=2;i<=n;++i)
for(int j=0;j<a[1];++j)
adde(j,(j+a[i])%a[1],a[i]);
dijkstra(0);
ll ans=0;
for(int i=0;i<a[1];++i){
if(dis[i]>r)continue;
ll L=max(l,dis[i])-1;
ans+=((r-i)/a[1])-((L-i)/a[1]);
}
printf("%lld",ans);
return 0;
}
P4156 [WC2016]论战捆竹竿
神仙题
首先KMP求出border之后就转化成了裸的同余最短路,但是复杂度爆炸,考虑优化建图
字符串的border有一个结论:
- 所有的border可以被划分为不超过\(O(\log n)\)个等差数列
(我太菜了不会证)
等差,又是同余的关系
设当前的等差数列为\(x,x+d,x+2d,\cdots,x+(l-1)\times d\),也就是首项为\(x\),公差为\(d\),项数为\(l\)的等差数列,在\(\bmod x\)意义下,会把所有\(\bmod x\)的余数分成\(\gcd(x,d)\)个环
对于每一个环,我们找到当前\(dis\)的最小值作为起点,\(dp\)更新剩下的。但是因为等差数列的长度有\(l\)的限制,并不是所有编号小于当前点的点都能够更新到自己。用单调队列来维护这个\(l\)的限制
现在考虑处理多个等差数列之间的转化,也就是不同模数之间的转化
设之前模数为\(p\),当前模数为\(x\)
首先\(dis_i\)肯定可以转移到\(dis_{dis_i \bmod x}\)上,同时我们还要考虑长为 \(u\) 的转移,我们可以将其看作首项为 \(0\),公差为 \(u\),长度为 \(1\) 的等差数列,用上面的类似方法完成即可,不需要单调队列。
inline void Min(ll &a,ll b){if(a>b)a=b;}
inline void Max(ll &a,ll b){if(a<b)a=b;}
inline int add(int a,int b,int mod){return (a+b>=mod)?a+b-mod:a+b;}
const int N=4e6+5;
//单调queue
struct Queue{
ll val[N];int id[N],head,tail;
inline void init(){head=1;tail=0;}
inline void push(ll x,int y){
while(tail>=head&&val[tail]>=x)--tail;
val[++tail]=x;id[tail]=y;
}
inline void pop(int x){while(head<tail&&id[head]<=x)++head;}
inline ll front(){return val[head];}
}q;
// kmp
int nxt[N],border[N],tot,n;
inline void kmp(char *s){
for(int i=2,j=0;i<=n;++i){
while(j&&s[j+1]!=s[i])j=nxt[j];
if(s[j+1]==s[i])++j;
nxt[i]=j;
}
tot=0;
for(int i=nxt[n];i;i=nxt[i])border[++tot]=n-i;border[++tot]=n;border[tot+1]=0;
}
int T;
ll m,dis[N],dis1[N];
int buc[N];
int curmod;
inline void init(){
memset(dis,0x3f,sizeof(dis));
dis[0]=0;curmod=0;
}
inline void change_mod(int x){
if(!curmod){curmod=x;return;}
memcpy(dis1,dis,curmod<<3);
memset(dis,0x3f,x<<3);
for(int i=0;i<curmod;++i){
int pos=dis1[i]%x;
Min(dis[pos],dis1[i]);
}
int mx=__gcd(x,curmod);
for(int i=0;i<mx;++i){
int cnt=1;
buc[1]=i;
for(int pos=add(i,curmod,x);pos!=i;pos=add(pos,curmod,x))
buc[++cnt]=pos;
for(int i=1;i<=cnt;++i)buc[i+cnt]=buc[i];cnt<<=1;
for(int i=2;i<=cnt;++i)Min(dis[buc[i]],dis[buc[i-1]]+curmod);
}
curmod=x;
}
int buc1[N];
inline void work(int x,int d,int len){
int mx=__gcd(x,d);
change_mod(x);
for(int i=0;i<mx;++i){
int cnt=1;
buc1[1]=i;
int st=1;
for(int pos=add(i,d,x);pos!=i;pos=add(pos,d,x)){
buc1[++cnt]=pos;
if(dis[pos]<dis[buc1[st]])st=cnt;
}
int cnt1=cnt;cnt=0;
for(int i=st;i<=cnt1;++i)buc[++cnt]=buc1[i];
for(int i=1;i<st;++i)buc[++cnt]=buc1[i];
q.init();
q.push(dis[buc[1]]-d,1);
for(int i=2;i<=cnt;++i){
int u=buc[i];
q.pop(i-len);
Min(dis[u],q.front()+1ll*i*d+1ll*x);
q.push(dis[u]-1ll*i*d,i);
}
}
}
char s[N];
int main(){
T=read();
while(T--){
n=read();m=readll();
scanf("%s",s+1);
if(m<n){puts("0");continue;}
m-=n;
init();
kmp(s);
for(int i=1,j=1;i<=tot;i=j+1){
j=i;
while(j+1<=tot&&border[i+1]-border[i]==border[j+1]-border[j])++j;
work(border[i],max(0,border[i+1]-border[i]),j-i+1);
}
ll ans=0;
for(int i=0;i<curmod;i++)if(dis[i]<=m)
ans+=(m-dis[i])/curmod+1;
printf("%lld\n",ans);
}
return 0;
}