P11626 [迷宫寻路 Round 3] 七连击

简要题意

任何数和 \(0\) 的最大公约数是它本身。

小 X 正在研究一个长度为 \(n\) 的数列 \(\{A\}\),他通过查阅资料,偶然间发现了一个叫做“七连击”的式子:\(\sum\limits_{a=1}^n\sum\limits_{b=a+1}^n\sum\limits_{c=b+1}^n\sum\limits_{d=c+1}^n\sum\limits_{e=d+1}^n\sum\limits_{f=e+1}^n\sum\limits_{g=f+1}^n ((\gcd\limits_{i=1}^aA_i)+(\gcd\limits_{i=a+1}^bA_i)+(\gcd\limits_{i=b+1}^cA_i)+(\gcd\limits_{i=c+1}^dA_i)+(\gcd\limits_{i=d+1}^eA_i)+(\gcd\limits_{i=e+1}^fA_i)+(\gcd\limits_{i=f+1}^gA_i))\)

其中 \((\gcd\limits_{i=l}^r A_i)\) 表示 \(A_l,A_{l+1},\dots,A_r\) 的最大公约数。

现在小 X 希望你求出这个式子的值。
由于答案可能很大,他只需要你输出答案对 \(998244353\) 取模的结果。

前置知识

简单数学,简单dp,前缀和优化。

思路

  1. 最为直观的dp一定是 \(dp_{i,j}\) 表示处理到第 \(i\) 个点分成 \(j\) 组的最大值。转移非常显然是枚举上一组的结尾位置,要算所有的情况所以还要有方案数再乘 \(\gcd\) 。然后对于一段区间的 \(gcd\) 通过 st表来维护。但是时间复杂已经 \(O(n^2)\) 了还有 \(7\) 的常数,考虑优化。

  2. 对于方案数我们,发现显然不用这么高的复杂度来维护,只需要对于再开一个数组记录为前缀和即可快速转移。但是对于 \(dp\) 数组就没有怎么简单了。

  3. 这时候如果你的数学非常的好的话,就可以观察到在右段点固定的时候区间的 \(\gcd\) 的不同取值只有 \(\log\) 种,对于所以有价值的转移点只有 \(\log\) 点,如果我们能提前处理找出这些点的话,时间复杂度就可以通过了。

  4. 那如何找到这些点,首先对于左端的值构成的序列是单调不降的,所以我们想到二分来找到每个边界情况,然后我们发现就会了。仔细分析一下时间复杂度发现可能是 \(O(n\log^3 n)\) ,转移点一个二分一个 \(gcd\) 一个。但是显然是跑不满的,还有说是两个 \(\log\) 的。

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353,N=1e5+10;
int n,lg[N],st[N][30],a[N],g[N][30],sumg[N][30],f[N][30],sumf[N][30];
struct node
{
	int l,r,v;
};
vector<node> ve[N];
void jia(int &a,int b)
{
	b=(b%mod+mod)%mod;
	a=(a+b)%mod; 
}
int query(int l,int r)
{
	int k=lg[r-l+1];
	return __gcd(st[l][k],st[r-(1<<k)+1][k]);
}
int se(int l,int r,int i,int v)
{
	int ans;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(query(mid,i)==v)ans=mid,r=mid-1;
		else l=mid+1; 
	} 
	return ans;
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);cout.tie(0);
	cin>>n;
	lg[0]=-1;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i],st[i][0]=a[i];
		lg[i]=lg[i/2]+1;
	}
	for(int j=1;j<=20;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			st[i][j]=__gcd(st[i][j-1],st[i+(1<<(j-1))][j-1]);
	//预处理出gcd的st表 
	
	for(int i=1;i<=n;i++)
	{
		for(int r=i,v=a[i];r>=2;)
		{
			v=__gcd(v,a[r]);
			int l=se(2,r,i,v);
			ve[i].push_back({l,r,v});
			r=l-1;
		} 
	} 
	//预处理出转移的位置
	for(int i=1;i<=n;i++)g[i][1]=1,sumg[i][1]=i;
	for(int i=2;i<=7;i++) 
		for(int j=1;j<=n;j++)
		{
			jia(g[j][i],sumg[j-1][i-1]);//前面任意位置分成i-1段 
			jia(sumg[j][i],sumg[j-1][i]+g[j][i]);//更新前缀和 
			//cout<<g[j][i]<<'\n';
		}
	//预处理出g数组 
	for(int i=1;i<=n;i++)f[i][1]=query(1,i),jia(sumf[i][1],sumf[i-1][1]+f[i][1]);
	for(int k=2;k<=7;k++)
	{
		for(int i=1;i<=n;i++)
		{
			jia(f[i][k],sumf[i-1][k-1]);
			for(auto j:ve[i])
			{
			//	cout<<j.l<<" "<<j.r<<" "<<j.v<<'\n';
				jia(f[i][k],j.v*(sumg[j.r-1][k-1]-sumg[j.l-2][k-1]+mod)%mod);
			}	
			jia(sumf[i][k],sumf[i-1][k]+f[i][k]);
		}			 
	} 
	//转移f数组
	int ans=0;
	for(int i=1;i<=n;i++)jia(ans,f[i][7]);
	cout<<ans<<'\n';
	return 0;
}
posted @ 2025-04-27 18:17  exCat  阅读(78)  评论(0)    收藏  举报