牛客练习赛9 F - 珂朵莉的约数

题目描述

珂朵莉给你一个长为n的序列,有m次查询

每次查询给两个数l,r

设s为区间[l,r]内所有数的乘积

求s的约数个数mod 1000000007

输入描述:

第一行两个正整数n,m第二行一个长为n的序列之后m行每行两个数l和r

输出描述:

对于每个询问,输出一个整数表示答案

示例1

输入

5 5
64 2 18 9 100
1 5
2 4
2 3
1 4
3 4

输出

165
15
9
45
10

备注:

对于100%的数据,有n , m <= 100000 , a[i] <= 1000000

题解

莫队算法,质因数分解。

本质就是求区间内每个素因子出现的次数的乘积,可以用莫队来搞。

然后超时了......分析了一波发现每个数最多可以分解成$20$个左右的素因子,也就是莫队增加一个数删除一个数的时候,需要进行$20$个数的修改。

常数有点大。又分析了一波,发现每个数最多只能分解成$7$个素因子,也就是按种类来修改可以减少一些常数。还是超时......

最后看了题解,发现好强啊......:

对每个数进行质因子分解,考虑一个数的约数个数即为其每个质因子出现次数+1的乘积,所以维护这个即可
考虑每个数只有一个大于1000的质数,对这部分进行根号分治
对于小于1000的质因子(只有168个),维护一个前缀和pre[i][j]表示第i个质因子在前j个数中出现次数
对于大于1000的质因子,用莫队维护即可

这样操作,莫队转移复杂度大大降低,小的素因子一波搞就$ok$了。

然后写莫队的时候发现了曾经没有注意到的事情,就是要先进行$add$,然后进行$delete$。不举例了。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 10;
const long long mod = 1000000007LL;
int a[maxn];
int b[maxn];
int sum[maxn][200];
int c[50], g;
int cnt[10 * maxn];

int pos[maxn];
int n, m, L, R;
long long Ans;

struct X {
  int l, r, id;
}s[maxn];
long long ans[maxn];

bool cmp(const X& a, const X& b) {
  if (pos[a.l] != pos[b.l]) return a.l < b.l;
  if((pos[a.l]) & 1) return a.r > b.r;
  return a.r < b.r;
}

bool noprime[10 * maxn];
int prime[10 * maxn], num_prime;
long long inv[20 * maxn];

void init() {
  inv[0] = inv[1] = 1;
  for(long long i = 2; i <= 2000005; i ++) {
    inv[i] = inv[mod % i] * (mod - mod / i) % mod;
  }
  noprime[1] = 1;
  for(int i = 2; i <= 1000000; i ++) {
    if(noprime[i]) continue;
    prime[num_prime ++] = i;
    for(int j = i + i ; j <= 1000000; j = j + i) {
      noprime[j] = 1;
    }
  }
}

void add(int x) {
  if(x == 0) return;
  Ans = Ans * inv[cnt[x] + 1] % mod;
  cnt[x] += 1;
  Ans = Ans * (cnt[x] + 1) % mod;
}

void del(int x) {
  if(x == 0) return;
  Ans = Ans * inv[cnt[x] + 1] % mod;
  cnt[x] -= 1;
  Ans = Ans * (cnt[x] + 1) % mod;
}

int main() {
  init();
  scanf("%d%d", &n, &m);
  int sz = sqrt(n);
  for(int i = 1; i <= n; i ++) {
    scanf("%d", &a[i]);
    pos[i] = i / sz;
    int now = 0;
    int tmp = a[i];
    g = 0;
    while(tmp != 1) {
      if(noprime[tmp] == 0) {
        c[g ++] = tmp;
        tmp = 1;
      } else {
        while(tmp % prime[now] == 0) {
          c[g ++] = prime[now];
          tmp = tmp / prime[now];
        }
        now ++;
      }
    }
    for(int j = 0; j < g; j ++) {
      if(c[j] > 1000) {
        b[i] = c[j];
      } else {
        for(int k = 0; k < 200; k ++) {
          if(prime[k] == c[j]) {
            sum[i][k] ++;
          }
        }
      }
    }
  }

  for(int i = 1; i <= n; i ++) {
    for(int j = 0; j < 200; j ++) {
      sum[i][j] = sum[i][j] + sum[i - 1][j];
    }
  }

  for(int i = 1; i <= m; i ++) {
    scanf("%d%d", &s[i].l, &s[i].r);
    s[i].id = i;
  }
  sort(s + 1, s + m + 1, cmp);

  Ans = 1LL;
  for(int i = s[1].l; i <= s[1].r; i ++) {
    add(b[i]);
  }
  for(int j = 0; j < 200; j ++) {
    Ans = Ans * (sum[s[1].r][j] - sum[s[1].l - 1][j] + 1) % mod;
  }

  ans[s[1].id] = Ans;
  L = s[1].l;
  R = s[1].r;
  for(int i = 2; i <= m; i ++) {
    while (L > s[i].l) { L--, add(b[L]); }
    while (R < s[i].r) { R++, add(b[R]); }
    while (L < s[i].l) { del(b[L]), L++; }
    while (R > s[i].r) { del(b[R]), R--; }

    for(int j = 0; j < 200; j ++) {
      Ans = Ans * inv[sum[s[i - 1].r][j] - sum[s[i - 1].l - 1][j] + 1] % mod;
      Ans = Ans * (sum[s[i].r][j] - sum[s[i].l - 1][j] + 1) % mod;
    }

    ans[s[i].id] = Ans;
  }
  for(int i = 1; i <= m; i ++) {
    printf("%lld\n", ans[i]);
  }
  return 0;
}

 

原文地址:https://www.cnblogs.com/zufezzt/p/8151313.html

时间: 2024-10-08 18:39:29

牛客练习赛9 F - 珂朵莉的约数的相关文章

牛客练习赛7 E 珂朵莉的数列(树状数组+爆long long解决方法)

https://www.nowcoder.com/acm/contest/38/E 题意: 思路: 树状数组维护.从大佬那里学习了如何处理爆long long的方法. 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 typedef long long ll; 7 const int maxn =

牛客练习赛11 B trie树+拓扑判环 E 分治求平面最近点对

牛客练习赛11 B  假的字符串题意:给定n个字符串,互不相等,你可以任意指定字符之间的大小关系(即重定义字典序),求有多少个串可能成为字典序最小的串,并输出它们. tags:好题 对于一个字符串, 1]如有其它字符串是它的前缀,那肯定不可能.这个直接用字典树处理就可以. 2]但如果以这个字符串为最小,怎么判定其它字符串不会矛盾呢? 其实矛盾的情况详细一点说是: 比如要以  abcd 为最小, 但又有另一个字符串 aba ,这就矛盾了. 对这种情况,在跑字典树的时候,我们对有相同父亲结点的多个儿

模板—珂朵莉树

其实本质上是优化暴力. 网上都说构造的数据可以卡掉珂朵莉树,是因为在修改的时候要遍历set导致很容易卡掉,所以珂朵莉树可能比较有局限性. 但是如果用来维护区间用于求交求并,复杂度是严格的log的,常数好像稍大,但是还是非常有用的. 放个板子: 1 #include<iostream> 2 #include<cstdio> 3 #include<set> 4 #define re register 5 #define co const 6 #define cor co r

[SHOI2015]脑洞治疗仪(线段树?珂朵莉树)

题面 这道题超级可爱呢,珂朵莉最可爱了,不,小哀才是最可爱的呢 很好的题,可以考虑用线段树维护,hale表示线段树思路很难,而且难打,不如滚去写珂朵莉树哦 对于操作一:直接将set修改插入即可 对于操作三:最大连续子段和(线段树里面是这样叫的吧)维护即可 对于操作二:我们发现可以考虑先将这段区间里面的1 全部取出来,然后暴力合并区间为0,插入会set里面 之后枚举要修改的区间,从左端点开始搞起,一直后搜索,最后加一个判断,是否已经完全ok即可,具体可参见代码 好了,这道题就解决了 我的代码好像l

P2787 语文1(chin1)- 理理思维(珂朵莉树)

珂朵莉树模板,区间排序就暴力地取二十六个字母出来并且计数,然后重新从小到大插入即可 代码: #include <bits/stdc++.h> #define int long long #define sc(a) scanf("%lld",&a) #define scc(a,b) scanf("%lld %lld",&a,&b) #define sccc(a,b,c) scanf("%lld %lld %lld"

好Van的珂朵莉树

珂朵莉树 珂朵莉树的主要操作是区间覆盖,即将区间\([l,r]\)全部染色为\(c\). EXAMPLE EXAMPLE 1 给出一个长度为\(n\)的序列,一共\(q\)次询问,每次询问给出\(m\)个区间,求这些区间并集的权值和. \(n \leq 10^5,\sum m \leq 10^5\) SOLUTION 1 显然能用珂朵莉树做 珂朵莉树是一种基于std::set的暴力数据结构. 这个set维护若干区间,这些区间没有交集,且按左端点从小到大有序. struct node { int

CF896C Willem, Chtholly and Seniorious 珂朵莉树

问题描述 CF896C LG-CF896C 题解 我expect就是T飞,从这里跳下去,也不碰和珂朵莉相关的任何东西. 珂朵莉树真好使. 珂朵莉树模板. \(\mathrm{Code}\) #include<bits/stdc++.h> using namespace std; #define int long long #define IT set<node>::iterator template <typename Tp> void read(Tp &x){

LG4979 矿洞:坍塌 珂朵莉树

问题描述 LG4979 题解 珂朵莉树+O2简直就是绝配 对于操作 A ,直接 \(\mathrm{assign}\) 推平就完事了. 对于操作 B ,如果它左右端点有在边界上的,直接把区间 \([l,r]\)撕出来判断就完了,如果不在边界上,先把单点 \({l-1,r+1}\) 撕出来判,如果符合条件,再撕 \([l,r]\) 出来判. \(\mathrm{Code}\) #include<bits/stdc++.h> using namespace std; #define IT set&

Solution: 题解 CF896C Willem, Chtholly and Seniorious(线段树解珂朵莉树)

Intro: 珂朵莉树模板题 怎么所有题解都是珂朵莉树啊啊啊啊 于是本蒟蒻决定来一发中(feng)规(kuang)中(luan)矩(gao)的线段树 首先这棵线段树只维护懒标记 来一发定义 线段树节点\(u\)维护区间\([l_u,r_u]\)的内容 懒标记\(t_u\):当\(t_u\not=0\)时表示区间\([l_u,r_u]\)全是\(t_u\),\(t_u=0\)就是没有懒标记 建立线段树 在建立时顺便处理\(l_u,r_u\),只要当\(l_u=r_u\)时就打上标记 P.s \(L