同余最短路

同余最短路

判断形如

[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;
}
原文地址:https://www.cnblogs.com/harryzhr/p/14703457.html