同余最短路学习笔记

同余最短路通常可以解决形如给出若干转移(例如加某个数、乘某个数),用它们拼出大整数而产生的问题。

其工作原理是选择一个模数 m,将整数分成 m 个同余类,将每个同余类看做一个状态。那么拼 x 的过程就相当于添一条路 ix(i+x)modm。然后根据每个状态的答案计算总答案。

P3403 跳楼机#

为了减少状态,可以取 m=min{x,y,z},被取到的值就不必建边了。建边后,从 1 开始跑最短路,得出每一个同余类中,最小的能到达的位置。设余数为 i 的同余类的最小位置为 di,显然 di+km 也都能到达。所以答案为

i=0m1[hdi](hdim+1)

代码太丑就不放了。有一些细节:m 可能为 1,所以要对 d1modm 赋初始值,并且最短路要开 long longP2371 [国家集训队]墨墨的等式 和这道题几乎完全一样。

ARC084B Small Multiple#

题目大意#

给定一个正整数 2K105,求它的所有正整数倍数中,十进制下数位和的最小值。

题解#

由于每个数都可以通过 +1×10 操作得到,并且其权值就是 +1 的操作数,所以直接把 K 当作模数,每个点添加两个转移,然后 01-BFS 即可。

#include<bits/stdc++.h>
using namespace std;
constexpr int N=1e5+5;
int n,d[N];
int main(){
	cin>>n;
	memset(d,0x3f,sizeof(d));
	deque<int>q;
	q.push_front(1);d[1]=1;
	while(q.size()){
		int u=q.front(),v;q.pop_front();
		v=(u*10)%n;
		if(d[u]<d[v]){
			d[v]=d[u];
			q.push_front(v);
		}
		v=(u+1)%n;
		if(d[u]+1<d[v]){
			d[v]=d[u]+1;
			q.push_back(v);
		}
	}
	printf("%d\n",d[0]);
	return 0;
}

P4156 [WC2016]论战捆竹竿#

题目大意#

给定一个长为 n 的字符串 s,其 公共前后缀(包含空串和整串) 从短到长分别为 b1,b2,,bk。记序列 a={n|bi|}。求在 [0,w] 中有多少个数能表示成 n+i=1kxiai,其中 xi 为自然数。

定理#

一个字符串 S 的 border 的长度有序排列成的序列能够被划分成 O(log|S|) 个等差数列。

证明#

我们知道,如果 s 有长为 xy 的周期且 x+ygcd(x,y)|s|,那么 gcd(x,y) 也是 s 的周期。具体证明可以用数学归纳法。

np,nq 分别为 s 的两个长度不小于 s 一半的 border 的长度,这样一来,s 长为 p 的前缀是 s 的周期,s 长为 gcd(p,q) 的前缀也是周期。

假设 nps 最长 border 的长度,那么 pq。于是就发现了一个公差为 p 的等差数列:s 长为 np,n2p, 的前缀都是 border。所以,所有长度大于等于一半的 border 的长度构成了等差数列。

我们知道 border 的 border 还是 border,所以对于长度小于一半的 border,可以递归应用这个性质,总共拆成 O(logn) 个等差数列。

题解#

尝试依次计算每个等差数列的贡献。

按照同余最短路的思想,记录一个数组 d,其中 di 表示在 modm 意义下余数为 i 的最小能到达的值。

考虑到等差数列 a,a+b,a+2b,,a+kb 时,先将 m 变成 a。这时要变化 d 数组。设新的 d 数组为 D,则 Di=mindji(moda)dj

然而这样显然不能得到所有的值。我们知道若 di 可达,则 di+km 可达,所以再建出边 im(i+m)moda,跑同余最短路。这些边形成了 gcd(a,m) 个环,所以我们有更好的方法:将每个环单独考虑,只有环内的节点能够互相影响。在环内找到 d 最小的点,往后更新即可。

然后就是在这上面用等差数列更新。模数被改成了 a,所以只要找到其中 b 形成的环,对于每个环找到最小的 di,然后更新整个环就行了。因为 b 有个数的限制,所以要用单调队列。

我在 UOJ 上卡了半天常数都没有过 Extra Test,然后去借鉴了 UOJ 上和我码风相似的代码,发现我统计等差数列时可能把同一个数统计在两个等差数列里,最多会有两倍常数。修改之后就过了。

最终代码如下。时间复杂度为 O(nlogn)

代码#

#pragma GCC optimize("Ofast,inline")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
constexpr int N=5e5+5;
constexpr ll INF=0x3f3f3f3f3f3f3f3f;
int n,m,nxt[N],bor[N],q[N],hd,tl,g[N],Sz;
ll w,dis[N],D[N],val[N];char s[N];
inline int Mod(const int&x){return x<m?x:x-m;}
inline void chkmin(ll&x,const ll&y){if(x>y)x=y;}
inline int prepare(int x){
	int ret=gcd(x,m),p=0;Sz=m/ret;x%=m;g[p++]=0;
	for(int u=x;u;u=Mod(u+x))g[p++]=u;
	return ret;
}
inline void getRing(){
	int k=0;for(int i=0;i<Sz;i++){
		g[i]++;if(g[i]==m)g[i]=0;
		if(dis[g[i]]<dis[g[k]])k=i;
	}
	rotate(g,g+k,g+Sz);
}
void updMod(int a){
	memset(D,0x3f,sizeof(ll[a]));
	for(int i=0;i<m;i++)chkmin(D[dis[i]%a],dis[i]);
	memcpy(dis,D,sizeof(ll[a]));
	int lst=m;m=a;for(int T=prepare(lst);;){
		for(int i=1;i<Sz;i++)chkmin(dis[g[i]],dis[g[i-1]]+lst);
		if(--T)getRing();else break;
	}
}
void add(int a,int d,int siz){
	if(m!=a)updMod(a);
	if(d<0)return;
	for(int T=prepare(d);;){
		ll delta=d;val[q[hd=tl=1]=0]=dis[g[0]]+a;
		for(int p=1,i;p<Sz;p++,delta+=d){
			while(hd<=tl&&p-siz>q[hd])hd++;
			i=g[p];chkmin(dis[i],val[q[hd]]+delta);
			while(hd<=tl&&val[q[tl]]>=dis[i]+a-delta)tl--;
			val[q[++tl]=p]=dis[i]+a-delta;
		}
		if(--T)getRing();else break;
	}
}
void ct(){
	scanf("%d%lld%s",&n,&w,s+1);w-=n;
	nxt[0]=-1;for(int i=1,j=-1;i<=n;i++){
		while(~j&&s[i]!=s[j+1])j=nxt[j];
		nxt[i]=++j;
	}
	memset(dis,0x3f,sizeof(ll[n]));dis[0]=0;int tot=0;
	for(int i=nxt[n];i;i=nxt[i])bor[tot++]=n-i;
	reverse(bor,bor+tot);m=n;
	for(int i=0,j;i<tot;i++){
		if(i==tot-1)updMod(bor[i]);else{
			for(j=i+1;j+1<tot&&bor[j]-bor[j-1]==bor[j+1]-bor[j];j++);
			add(bor[j],bor[j-1]-bor[j],j-i);
			i=j;
		}
	}
	ll ans=0;
	for(int i=0;i<m;i++)if(w>=dis[i])ans+=(w-dis[i])/m+1;
	printf("%lld\n",ans);
}
int main(){
	int T;scanf("%d",&T);while(T--)ct();
	return 0;
}
posted @   hihihi198  阅读(59)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
主题色彩