同余最短路通常可以解决形如给出若干整数,用它们拼出大整数而产生的问题。
其工作原理是选择一个模数 \(m\),将整数分成 \(m\) 个同余类,将每个同余类看做一个状态。那么拼 \(x\) 的过程就相当于添一条路 \(i\xrightarrow{x}(i+x)\bmod m\)。然后根据每个状态的答案计算总答案。
P3403 跳楼机
为了减少状态,可以取 \(m=\min\{x,y,z\}\),被取到的值就不必建边了。建边后,从 \(1\) 开始跑最短路,得出每一个同余类中,最小的能到达的位置。设余数为 \(i\) 的同余类的最小位置为 \(d_i\),显然 \(d_i+km\) 也都能到达。所以答案为
\[\sum_{i=0}^{m-1}[h\ge d_i]\left(\left\lfloor\frac{h-d_i}{m}\right\rfloor+1\right) \]代码太丑就不放了。有一些细节:\(m\) 可能为 \(1\),所以要对 \(d_{1\bmod m}\) 赋初始值,并且最短路要开 long long
。P2371 [国家集训队]墨墨的等式 和这道题几乎完全一样。
P4156 [WC2016]论战捆竹竿
题目大意
给定一个长为 \(n\) 的字符串 \(s\),其 公共前后缀(包含空串和整串) 从短到长分别为 \(b_1,b_2,\cdots,b_k\)。记序列 \(a=\{n-|b_i|\}\)。求在 \([0,w]\) 中有多少个数能表示成 \(n+\sum_{i=1}^kx_ia_i\),其中 \(x_i\) 为自然数。
定理
一个字符串 \(S\) 的 border 的长度有序排列成的序列能够被划分成 \(O(\log |S|)\) 个等差数列。
证明
我们知道,如果 \(s\) 有长为 \(x\) 和 \(y\) 的周期且 \(x+y-\gcd(x,y)\le |s|\),那么 \(\gcd(x,y)\) 也是 \(s\) 的周期。具体证明可以用数学归纳法。
设 \(n-p,n-q\) 分别为 \(s\) 的两个长度不小于 \(s\) 一半的 border 的长度,这样一来,\(s\) 长为 \(p\) 的前缀是 \(s\) 的周期,\(s\) 长为 \(\gcd(p,q)\) 的前缀也是周期。
假设 \(n-p\) 是 \(s\) 最长 border 的长度,那么 \(p\mid q\)。于是就发现了一个公差为 \(-p\) 的等差数列:\(s\) 长为 \(n-p,n-2p,\cdots\) 的前缀都是 border。所以,所有长度大于等于一半的 border 的长度构成了等差数列。
我们知道 border 的 border 还是 border,所以对于长度小于一半的 border,可以递归应用这个性质,总共拆成 \(O(\log n)\) 个等差数列。
题解
尝试依次计算每个等差数列的贡献。
按照同余最短路的思想,记录一个数组 \(d\),其中 \(d_i\) 表示在 \(\bmod m\) 意义下余数为 \(i\) 的最小能到达的值。
考虑到等差数列 \(a,a+b,a+2b,\cdots,a+kb\) 时,先将 \(m\) 变成 \(a\)。这时要变化 \(d\) 数组。设新的 \(d\) 数组为 \(D\),则 \(D_i=\min_{d_j\equiv i\pmod a}d_j\)。
然而这样显然不能得到所有的值。我们知道若 \(d_i\) 可达,则 \(d_i+km\) 可达,所以再建出边 \(i\xrightarrow{m}(i+m)\bmod a\),跑同余最短路。这些边形成了 \(\gcd(a,m)\) 个环,所以我们有更好的方法:将每个环单独考虑,只有环内的节点能够互相影响。在环内找到 \(d\) 最小的点,往后更新即可。
然后就是在这上面用等差数列更新。模数被改成了 \(a\),所以只要找到其中 \(b\) 形成的环,对于每个环找到最小的 \(d_i\),然后更新整个环就行了。因为 \(b\) 有个数的限制,所以要用单调队列。
我在 UOJ 上卡了半天常数都没有过 Extra Test,然后去借鉴了 UOJ 上和我码风相似的代码,发现我统计等差数列时可能把同一个数统计在两个等差数列里,最多会有两倍常数。修改之后就过了。
最终代码如下。时间复杂度为 \(O(n\log n)\)。
代码
#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;
}
标签:int,短路,笔记,dis,border,等差数列,同余
From: https://www.cnblogs.com/hihihi198/p/17052627.html