离线求逆元
我们先看一道题
看到这道题,我们首先考虑线性求逆的复杂度\(O(max(a_{i}))\),在这道题中显然不是很优,因为时间空间都会被卡(而且出题人也不傻,怎么可能乘法逆元一的正解就是乘法逆元二的呢)
我们再考虑一下费马小定理和拓展欧几里得的\(O(n\ (log\ p))\),貌似更炸.
然后我们讲完离线逆元之后我们再考虑一下这个神奇的算法的可行性
首先我们要知道一个事情就是:前缀积的逆元就是逆元的前缀积(即逆元是完全积性的)
证:我们设任意两个整数\(a\)和\(b\),我们看一下他们在模\(p\)意义下一定满足\(a^{-1}*b^{-1}\equiv (a*b)^{-1}\ (mod\ p)\)
我们根据逆元的定义可以得到柿子\(a*a^{-1}*b*b^{-1}\equiv 1\ (mod\ p)\)
然后我们把\(a*b\)看做一个整体,然后这个整体的逆元就是\((a*b)^{-1}\)
我们可以知道\((a*b)*(a*b)^{-1}\equiv 1\ (mod\ p)\)
∴\((a^{-1}*b^{-1})\equiv (a*b)^{-1}\ (mod\ p)\)
根据这个性质,我们可以用一个\(pre_{n}\)数组来存储\(a_{n}\)的前缀积(不是前缀和!!!)
∵\(\displaystyle pre_{n}=\prod_{i=1}^{n}a_{i}\)
∴\(\displaystyle pre_{n}^{-1}=\prod_{i=1}^{n}a_{i}^{-1}\)
然后我们就会发现:
\(a_{i}^{-1}=pre_{i}^{-1}*pre_{i-1}\ (1<=i<=n)\)
\(pre_{i}^{-1}=pre_{i+1}^{-1}*a_{i+1}\ (1<=i<n)\)
然后我们是不是阔以根据这两个柿子来做题了呢
我们考虑一下他的时间复杂度:线性的枚举\(O(n)\)+费马小定理(或拓展欧几里得)求\(pre_{n}^{-1}\)的\(O(log\ p)\)
时间复杂度为:\(O(n+log\ p)\)
但是这道题还会卡常,所以我们还要用快读
因为这道题最后要求的是\(\displaystyle\sum_{i=1}^{n}{\frac {k^{i}}{a_{i}}}\),所以我们可以转换为\(\displaystyle \sum_{i=1}^{n}{k^{i}*a_{i}^{-1}}\)
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define LL long long
const LL p = 1e9+7;
LL n,ans,a[5000010],pre[5000010],inv[5000010];
inline LL read()
{
int s = 0, w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10+ch -'0'; ch = getchar();}
return s * w;
}
LL ksm(LL a, LL b)
{
LL res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = res * a % p;
a = a * a % p;
}
return res;
}
int main()
{
n = read(); pre[0] = 1;
for(int i = 1; i <= n; ++i)
{
a[i] = read();
pre[i] = pre[i-1] * a[i] % p;
}
inv[n] = ksm(pre[n],p-2);
for(int i = n; i >= 1; --i)
{
inv[i-1] = inv[i] * a[i] % p;
}
LL tmp = 1;
for(int i = n; i >= 1; --i)
{
ans = (ans + inv[i] * pre[i-1] % p * tmp % p) % p;
tmp = tmp * 998244353 % p;
}
printf("%lld\n",ans);
return 0;
}