同余最短路学习笔记
同余最短路通常可以解决形如给出若干转移(例如加某个数、乘某个数),用它们拼出大整数而产生的问题。
其工作原理是选择一个模数 ,将整数分成 个同余类,将每个同余类看做一个状态。那么拼 的过程就相当于添一条路 。然后根据每个状态的答案计算总答案。
P3403 跳楼机#
为了减少状态,可以取 ,被取到的值就不必建边了。建边后,从 开始跑最短路,得出每一个同余类中,最小的能到达的位置。设余数为 的同余类的最小位置为 ,显然 也都能到达。所以答案为
代码太丑就不放了。有一些细节: 可能为 ,所以要对 赋初始值,并且最短路要开 long long
。P2371 [国家集训队]墨墨的等式 和这道题几乎完全一样。
ARC084B Small Multiple#
题目大意#
给定一个正整数 ,求它的所有正整数倍数中,十进制下数位和的最小值。
题解#
由于每个数都可以通过 和 操作得到,并且其权值就是 的操作数,所以直接把 当作模数,每个点添加两个转移,然后 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]论战捆竹竿#
题目大意#
给定一个长为 的字符串 ,其 公共前后缀(包含空串和整串) 从短到长分别为 。记序列 。求在 中有多少个数能表示成 ,其中 为自然数。
定理#
一个字符串 的 border 的长度有序排列成的序列能够被划分成 个等差数列。
证明#
我们知道,如果 有长为 和 的周期且 ,那么 也是 的周期。具体证明可以用数学归纳法。
设 分别为 的两个长度不小于 一半的 border 的长度,这样一来, 长为 的前缀是 的周期, 长为 的前缀也是周期。
假设 是 最长 border 的长度,那么 。于是就发现了一个公差为 的等差数列: 长为 的前缀都是 border。所以,所有长度大于等于一半的 border 的长度构成了等差数列。
我们知道 border 的 border 还是 border,所以对于长度小于一半的 border,可以递归应用这个性质,总共拆成 个等差数列。
题解#
尝试依次计算每个等差数列的贡献。
按照同余最短路的思想,记录一个数组 ,其中 表示在 意义下余数为 的最小能到达的值。
考虑到等差数列 时,先将 变成 。这时要变化 数组。设新的 数组为 ,则 。
然而这样显然不能得到所有的值。我们知道若 可达,则 可达,所以再建出边 ,跑同余最短路。这些边形成了 个环,所以我们有更好的方法:将每个环单独考虑,只有环内的节点能够互相影响。在环内找到 最小的点,往后更新即可。
然后就是在这上面用等差数列更新。模数被改成了 ,所以只要找到其中 形成的环,对于每个环找到最小的 ,然后更新整个环就行了。因为 有个数的限制,所以要用单调队列。
我在 UOJ 上卡了半天常数都没有过 Extra Test,然后去借鉴了 UOJ 上和我码风相似的代码,发现我统计等差数列时可能把同一个数统计在两个等差数列里,最多会有两倍常数。修改之后就过了。
最终代码如下。时间复杂度为 。
代码#
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现