智乃与无意义的题目(数论结论+树状数组/线段树)2020西北工业大学竞赛

1 篇文章 0 订阅

呐~原题链接

题目描述

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∣n​1(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的作用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值