同余最短路

同余最短路

判断形如

\[\sum a_ix_i=h \]

的方程是否有自然数解

算法流程

我们找到所有的\(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;
}
posted @ 2021-04-26 10:14  harryzhr  阅读(45)  评论(0编辑  收藏  举报