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,前缀和优化。
思路
-
最为直观的dp一定是 \(dp_{i,j}\) 表示处理到第 \(i\) 个点分成 \(j\) 组的最大值。转移非常显然是枚举上一组的结尾位置,要算所有的情况所以还要有方案数再乘 \(\gcd\) 。然后对于一段区间的 \(gcd\) 通过 st表来维护。但是时间复杂已经 \(O(n^2)\) 了还有 \(7\) 的常数,考虑优化。
-
对于方案数我们,发现显然不用这么高的复杂度来维护,只需要对于再开一个数组记录为前缀和即可快速转移。但是对于 \(dp\) 数组就没有怎么简单了。
-
这时候如果你的数学非常的好的话,就可以观察到在右段点固定的时候区间的 \(\gcd\) 的不同取值只有 \(\log\) 种,对于所以有价值的转移点只有 \(\log\) 点,如果我们能提前处理找出这些点的话,时间复杂度就可以通过了。
-
那如何找到这些点,首先对于左端的值构成的序列是单调不降的,所以我们想到二分来找到每个边界情况,然后我们发现就会了。仔细分析一下时间复杂度发现可能是 \(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;
}

浙公网安备 33010602011771号