[迷宫寻路 Round 3] 七连击

转化题意:求将一段序列划分为8段,求所有方案的前七段的每一段gcd的和的和.

首先朴素的dp很容易想到,设\(dp(i,j)\)为将前\(i\)位划分为前\(j\)段的答案,\(g(i,j)\)为将前\(i\)位划分为前\(j\)段的方案数.

于是有

\[\begin{aligned} g_{i,j} &= \sum_{k = 1}^{i-1} g_{k,j-1} \\ f_{i,j} &= \sum_{k = 1}^{i-1} f_{k,j-1}+g_{k,j-1}\times gcd(k+1,i) \end{aligned} \]

注:\(gcd(i,j)\)在本文中为序列第\(i\)为到第\(j\)位的\(gcd\)

其中\(gcd\)部分可用ST表\(O(nlogn)\)预处理后,\(O(1)\)求出,\(g\)数组也很显然前缀和\(O(7n)\)求出,接下来只需要考虑\(f\)的处理

拆开有:

\[\begin{aligned} f_{i,j} &=\sum_{k = 1}^{i-1} f_{k,j-1}+\sum_{k = 1}^{i-1} g_{k,j-1}\times gcd(k+1,i)\end{aligned} \]

左侧很显然可以前缀和\(O(7n)\)求.

右侧若无\(gcd\)这一项,那也可以前缀和,那如何去掉\(gcd\)的影响呢?

考虑当\(i\)确定时,\(gcd(k+1,i)\)最多只有\(log\)种取值,因为每次变化至少会少一个质因子而不会增加质因子.故考虑二分变化点,这样其中的每一段区间中\(gcd\)是不变的,是一个常量,故\(f_{i,j}\)可以通过\(log\)次转移求出,总时间复杂度\(O(7nlogWlogn)\).

注:这种做法对于序列中值为0的情况是时间复杂度没有任何影响的,因为是区间\(gcd\)所以还是满足最多只有\(log\)种情况.(我写代码时还加了特判,特判还算错了)

故本题的核心思想是通过前缀和优化转移的拓展,即通过了转移方程中\(gcd\)的极大的重复性使得花费了\(log\)的代价让他变成了常数,以此来通过前缀和优化.
还有就是ST表在查询次数多的时候一定不要用log2函数,一定要用lg数组线性递推,这样才能保证常数不会爆炸.
code:

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+15,mod = 998244353,SZ = 2.2e6+15;
int gcd(int a,int b){
	if(b==0) return a;
	return gcd(b,a%b);
}
int add(int a,int b){ return a+b < mod ? a+b : a+b-mod;}
int sub(int a,int b){ return a<b ? a+mod-b : a-b;}
int mul(int a,int b){ return 1ll*a*b%mod;}
char buf[SZ],*pp=buf-1;
int in(){
	while(*++pp<'-');
	int w = *pp&15;
	while(*++pp>'-') w = (w<<3)+(w<<1)+(*pp&15);
	return w;
}
int n,a[N],f[N][10],sum[N],g[N][10],st[N][25],lg[N];
int query(int l,int r){
	int s = lg[r-l+1];
	return gcd(st[l][s],st[r-(1<<s)+1][s]);
}
int find(int i,int x){
	int l = 1 , r = i , ret = 0;
	while(l<=r){
		int mid=(l+r)>>1;
		if(query(mid+1,x)==query(i+1,x)) r = mid-1 , ret = mid;
		else l = mid+1;
	}
	return ret;
}
int main(){
	fread(buf,1,SZ,stdin);
	n=in();
	for(int i = 2 ; i<=n ; i++) lg[i] = lg[i>>1]+1;
	for(int i = 1 ; i<=n ; i++) a[i]=in(),st[i][0]=a[i];
	for(int i = 1 ; i<=n ; i++) g[i][1]=1;
	for(int i = 2 ; i<=7 ; i++){
		for(int j = 1 ; j<=n ; j++){
			g[j][i]=add(g[j-1][i],g[j-1][i-1]);
		}
	}
	int logn = lg[n];
	for(int i = 1 ; i<=logn ; i++){
		for(int j = 1 ; j+(1<<i)-1<=n ; j++){
			st[j][i]=gcd(st[j][i-1],st[j+(1<<(i-1))][i-1]);
		}
	}
	for(int i = 1 ; i<=n ; i++) f[i][1] = query(1,i);
	for(int j = 2 ; j<=7 ; j++){
		for(int i = 1 ; i<=n ; i++) f[i][j] = add(f[i-1][j],f[i-1][j-1]) , sum[i]=add(sum[i-1],g[i][j-1]) ;
		for(int i = 1 ; i<=n ; i++){
			int k = i-1;
			while(k){
				int tmp = find(k,i);
				f[i][j] = add(f[i][j],mul(query(tmp+1,i),sub(sum[k],sum[tmp-1])));
				k = tmp-1;
			}
		}
	}
	int ans = 0;
	for(int i = 1 ; i<=n ; i++) ans = add(ans,f[i][7]);
	printf("%d",ans);
	return 0;
}
posted @ 2025-10-08 17:56  lrj3247  阅读(2)  评论(0)    收藏  举报