同余最短路
判断形如
[sum a_ix_i=h
]
的方程是否有自然数解
算法流程
我们找到所有的(a_i)中的最小值作为模数,如果一个数(p)可以被构造,那么(p+k imes a_1)也就一定可以被构造
也就是对于每一个余数(i),我们需要找到最小的,(p mod a_1=i)的(p)
每加上一个数(a_j),我们可以实现(pmod a_1=i)到(pmod a_1=(i+a_j)mod a_1)的转化,所以连边(i o (i+a_j)mod 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) imes d),也就是首项为(x),公差为(d),项数为(l)的等差数列,在(mod x)意义下,会把所有(mod x)的余数分成(gcd(x,d))个环
对于每一个环,我们找到当前(dis)的最小值作为起点,(dp)更新剩下的。但是因为等差数列的长度有(l)的限制,并不是所有编号小于当前点的点都能够更新到自己。用单调队列来维护这个(l)的限制
现在考虑处理多个等差数列之间的转化,也就是不同模数之间的转化
设之前模数为(p),当前模数为(x)
首先(dis_i)肯定可以转移到(dis_{dis_i mod 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
",ans);
}
return 0;
}