呐~原题链接
题目描述
Chino的数论很差,因此Cocoa非常担心。这一天,Cocoa给了Chino一道非常meaningless的数论题: 有n(1≤n≤105)个数a1⋯n(1≤ai≤10)以及q(1≤q≤105)个操作,操作分为两种:
1 p v,令ap(1≤p≤n)=v(1≤v≤10)
2 l r,查询f(al×al+1×⋯×ar)(1≤l≤r≤n),其中f(n)=∑i∣n1(n的因数个数) 对于每个2 l r,你需要给出答案。由于答案可能会很大,因此你只需要输出 mod 998244353后的值即可。
虽然题目非常meaningless,但是对于Chino来说还是太难啦!你能帮一帮她吗?
输入描述:
输入的第一行有两个数n,m,表示数列的长度和询问的个数;接下来一行有n个数aia_iai;接下来m行每行包含了一个1 p v或者2 l r.
输出描述:
对于每个2 l r,你需要输出答案,每个答案独占一行。
题解一
思路简单算法时间复杂度高O(n*n)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = (int)1e9 + 7;
const int inf = 0x3f3f3f3f; //初始化无穷大
int a[100005];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n, num;
cin >> n >> num;
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
}
while (num)
{
int b, c, d;
cin >> b >> c >> d;
if (b == 1)
{
a[c] = d;
}
if (b == 2)
{
ll ans = 1;
for (int i = c; i <= d; ++i)
ans *= a[i];
ll count = 0;
ll cc = sqrt(ans);
for (ll i = 1; i < cc; ++i)
{
if (ans % i == 0)
{
count += 2;
count %= 998244353;
}
}
if (cc * cc == ans)count += 1;
if (cc * cc < ans && (ans % cc == 0))
{
count += 2;
}
cout << count % 998244353 << endl;
}
--num;
}
return 0;
}
题解二
注意到f(n) = Ein 1实际上就是n的本质不同的约数个数,那么根据质因数分解n= p1 p2… pim,我们可以利用一点组合数学的知识算出来: f(n)=(1 + k1)(1 + k2)…(1 + km).这一点用图来理解一下
考虑维护区间乘积的质因数分解,它和区间中每个数的质因数分解的乘积是等价的。也就是不是单纯的求一个数中质因数2 3 5 7的个数,而是求一个区间的2 3 5 7 的个数和。考虑到数字不超过10,因此质因数只有2、3、5、7,那么只需要开四棵树状数组或者线段树分别维护即可(下列代码示例用的是树状数组),假设对于有相同质因数pm的ai, aj,假设在ai中pm的幂是ks,在aj中是kt,那么在乘积中就是ks + kt .(意思就是,对于两个数a,b,假设a中2的幂是3,b中2的幂是4,那么乘积a*b中2的幂就是2+4).这样我们只需要分别在每棵线段树(树状数组)上分别维护区间质因数和,然后把区间质因数和+1的结果相乘即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = (int)1e5 + 500;
const int mod = 998244353;
template<typename tp>inline tp lowbit(tp x) { return x & (-x); }
int a[maxn];//存输入进去的数组数值
ll sum[11][maxn];//sum[i][j]表示树状数组中第j个节点中包含的[i]的个数,这里其实只用到了i=2 ,3 ,5 ,7
//将val加到树状数组第p个节点及包含p节点的节点中x对应的个数里
void add(int p, int x, int val)
{
while (p <= maxn)
{
sum[x][p] += val;
p += lowbit(p);//p所管辖的范围,不懂的去看看树状数组的知识
}
}
ll ask(int p, int x)
{
ll res = 0;
while(p)
{
res += sum[x][p];
p -= lowbit(p);
}
return res;
}
//将树状数组第p个节点及包含第p个节点的节点初始化为x中2 ,3,5,7的个数,注意这里的个数指的是幂次方
void Add(int x, int p)
{
if (x == 2 || x == 3 || x == 5 || x == 7)add(p, x, 1);
else {
if (x == 4)add(p, 2, 2);
if (x == 6) {
add(p, 2, 1);
add(p, 3, 1);
}
if (x == 8)add(p, 2, 3);
if (x == 9)add(p, 3, 2);
if (x == 10) {
add(p, 2, 1);
add(p, 5, 1);
}
}
}
//将第p个及包含第p个节点的节点中x包含的2,3,5,7的个数全部删掉
void Sub(int x, int p) {
if (x == 2 || x == 3 || x == 5 || x == 7)add(p, x, -1);
else {
if (x == 4)add(p, 2, -2);
if (x == 6) {
add(p, 2, -1);
add(p, 3, -1);
}
if (x == 8)add(p, 2, -3);
if (x == 9)add(p, 3, -2);
if (x == 10) {
add(p, 2, -1);
add(p, 5, -1);
}
}
}
void solve() {
int n, m;
cin >> n >> m;
ll two = 0, three = 0, five = 0, seven = 0;
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
Add(a[i], i);//将树状数组第i个节点及包含第i个节点的节点初始化为a[i]中2 ,3,5,7的个数
}
for (int i = 1; i <= m; i++)
{
int opt;//选择操作变量
cin >> opt;
int l, r;
cin >> l>>r;
if (opt == 1)
{
Sub(a[l], l);//将第l个及包含第l个节点的节点中a[l]包含的2,3,5,7的个数全部删掉
a[l] = r;
Add(a[l], l);//将第l个及包含第l个节点的节点中a[l]包含的2,3,5,7的个数全部添加,则更新完毕
}
else {
two = ask(r, 2) - ask(l - 1, 2);//统计范围内2的个数,下面同理
three = ask(r, 3) - ask(l - 1, 3);
five = ask(r, 5) - ask(l - 1, 5);
seven = ask(r, 7) - ask(l - 1, 7);
ll ans = (two + 1) % mod * (three + 1) % mod * (five + 1) % mod * (seven + 1) % mod;//所求结果
cout << ans << endl;
}
}
}
int main()
{
solve();
return 0;
}
看这个树状数组可以发现,我们每修改一个a[i],包含a[i]的相应区间都要修改他的sum[2],sum[3],sum[5],sum[7]的个数。对于求一个区间里面2,3,5,7的个数和的时候,比如要求a[2]-a[4]区间数值乘积的因数个数,我们只需要用sum[i][4]-sum[i][1]就好,i对应2,3,5,7,这就是函数ask的作用