首页 > 其他分享 >同余最短路学习笔记

同余最短路学习笔记

时间:2023-01-14 21:58:17浏览次数:59  
标签:int 短路 笔记 dis border 等差数列 同余

同余最短路通常可以解决形如给出若干整数,用它们拼出大整数而产生的问题。

其工作原理是选择一个模数 \(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 longP2371 [国家集训队]墨墨的等式 和这道题几乎完全一样。

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

相关文章

  • “动手学强化学习Pytorch版”笔记
    书籍一:2.3.3梯度:梯度就是对张量中的每个变量都求偏导,求出某点的值,然后将他们按照原先张量的对应顺序写成一个新张量,这个新张量就是原先张量在某点的梯度如:importtor......
  • 线段树 复习笔记
    线段树是一类处理区间问题的优秀算法,通过用空间换时间来得到相对平均的复杂度。同时,也是一个OIer从萌新到提高的重要过渡。1.线段树的基本概念不难看出线段树是一棵......
  • 笔记本在使用一段时间后莫名卡顿的解决方法——CPU莫名锁频
    首先要看看是不是因为CPU锁频了。键盘按下Ctrl+Shift+Esc,看看CPU内的速度部分是否保持在零点几。解决方案如果是的话,可以先将电脑关机,然后将所有的外设拔掉,电源也拔掉......
  • Google_Book_20Things.前言以及前四项学习笔记
    20THINGSILEARNEDABOUTBROWSERSANDTHEWEBIllustratedbyChristophNiemann.WrittenbytheGoogleChromeTeam.关于浏览器与网络的20件事前言(Foreword)任何......
  • 线段树优化建图学习笔记
    使用场景:单点向区间,区间向单点或区间向区间建边。实现原理:用线段树的一个节点管辖一段图上区间的顶点。实现步骤:将原图中的顶点拆点(理论上,实际代码中不需要),出点和入点......
  • Redis 6 学习笔记 3 —— 用SpringBoot整合Redis的踩坑,了解事务、乐观锁、悲观锁
    SpringBoot整合Redis时踩到的坑jdk1.8环境,用idea的SpringInitializr创建springboot项目,版本我选的2.7.6。pom文件添加的依赖如下,仅供参考。注意commons-pool2选错版本......
  • vue-cli笔记
    一、安装全局安装npminstall-g@vue/cli创建项目vuecreate项目名二、目录介绍src--main.js入口文件main.js介绍//引入vueimportVuefrom'vue'/......
  • 《态度-吴军》 读书笔记
    教育首先是关怀备至地、深思熟虑地、小心翼翼地去触及年轻的心灵。——苏霍姆林斯基一、前言《态度》一书为吴军老师给上高中和大学的两个女儿写的四十封家书,针对成长过......
  • linux工具grep的使用心得笔记
    grep作为linux管理中常用的三大工具之一(grep、awk、sed),其功能十分强大,因此难以对其进行全面的使用介绍,因此本文只作为个人学习的笔记之用。 grep的用处:在文本中匹配要......
  • 数论学习笔记
    逆元定义存在$a\timesx\equiv1\pmod{m}$,我们称\(x\)为\(a\)的逆元。完全剩余系假设\(1\toP-1\)都存在逆元,我们则称他为一个完全剩余系。当......