【TsinsenA1339】JZPLCM(顾昱洲) 树状数组

试题来源

  2012中国国家集训队命题答辩

问题描述

  给定一长度为n的正整数序列a,有q次询问,每次询问一段区间内所有数的lcm(即最小公倍数)。由于答案可能很大,输出答案模1000000007。

输入格式

  第一行,两个整数,n, q,分别表示数列长度和询问个数。
  下面n行,每行一个整数,第i行的整数为ai。
  下面q行,每行两个整数l, r,表示询问下标i在[l, r]范围内的ai的lcm。

输出格式

  q行。对于每个询问,输出一行,表示对应的答案。

样例输入

3 3
123
234
345
1 2
2 3
1 3

样例输出

9594
26910
1103310

数据规模和约定

测试数据编号 规模和约定
1, 2 n, q<=1000
3, 4 n, q<=5000
5, 6 n, q<=20000
7, 8 n, q<=30000
9, 10 n, q<=40000
11, 12 n, q<=50000
13, 14 n, q<=80000
15, 16 n, q<=100000
17, 18, 19, 20 n, q<=100000 数列a中每个数能表示为不超过100的素数的积

对于所有测试点,数列a中每个数满足1<=ai<=1000000000。

求LCM会爆LL,所以可以考虑分解质因数然后求最大幂然后乘起来。

40分算法:

对于最后四个点:
考虑对每个素数的幂建一个ST表,则答案是每个素数的幂的区间最大值,乘起来就行了。复杂度 O(25nlogn+25q)

对于前四个点:
暴力分解质因数,求最大的幂。复杂度 O(nqlogn)

60分算法:

考虑离线莫队。发现每次区间转移相当于删除/加入log个质数,最后询问是询问每个质数的最大的幂,可以用平衡树来维护,复杂度 O(nnlog2n)

100分算法:

考虑把一个数拆成多个质数相乘,若有一个因子是 pk ,则拆成k个数,分别为 p1,p2...pk ,每个数的权值都为p。这样转化成询问区间内不同的数的权值的乘积。

正确性: px!=py (x!=y) ,若区间内同时出现两个数都有p的因子,则幂高的把幂低的覆盖掉了,每次都是统计的幂最高的数的贡献。

转化的问题可以离线+树状数组求解。

做法:

对于一个数x,记它上一次出现的位置为last[x],x的权值是val[x]。
离线操作是按右端点排序,然后左端点只负责询问,右端点的右移是不断加点的过程。
考虑前缀的区间减法,这样就能树状数组了。
每次加入一个数x,则要让 last[x]=last[x]1/val[x] ,当前位置乘val[x]。支持的查询操作是查询前缀的乘积,然后求个逆元就行了。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;

typedef long long LL;
const int SZ = 3000010;
const int INF = 1000000010;
const int mod = 1000000007;

int tot = 0,cnt = 0;
struct squ{
    int id,num,val,last;
}a[SZ];

struct que{
    int l,r,id;
    LL ans;
}ask[SZ];

bool cmp(que a,que b) { return a.r < b.r; }
bool cmp2(que a,que b) { return a.id < b.id; }

map<int,int> mp; //分配种类 

namespace init{

    bool vis[SZ];
    int pri[SZ];

    void gen(int n)
    {
        vis[1] = 1;
        for(int i = 2,tot = 0;i <= n;i ++)
        {
            if(!vis[i]) pri[++ tot] = i;
            for(int j = 1,m;j <= tot && (m = i * pri[j]) <= n;j ++)
            {
                vis[m] = 1;
                if(i % pri[j] == 0) break;
            }
        }
    }

    void fj(int x,int id)
    {
        for(int i = 1;(LL)pri[i] * pri[i] <= x;i ++)
        {
            int t = 1;
            while(x % pri[i] == 0)
            {
                t = t * pri[i];
                if(!mp[t]) mp[t] = ++ cnt;
                a[++ tot] = (squ){id,mp[t],pri[i],0};
                x /= pri[i];
            }
        }
        if(x != 1)
        {
            if(!mp[x]) mp[x] = ++ cnt;
            a[++ tot] = (squ){id,mp[x],x,0};
        }
    }
}

LL sum[SZ];
int n,m;

void mult(int x,int d)
{
    if(x == 0) return ;
    for(int i = x;i <= n;i += i & -i)
        sum[i] = (LL)sum[i] * d % mod;
}

int ask_ans(int x)
{
    int ans = 1;
    for(int i = x;i > 0;i -= i & -i)
        ans = (LL)ans * sum[i] % mod;
    return ans;
}

LL ksm(LL a,LL b)
{
    LL ans = 1;
    while(b)
    {
        if(b & 1) ans = (LL)ans * a % mod;
        a = (LL)a * a % mod;
        b >>= 1;
    }
    return ans;
}

int ni(int x)
{
    return ksm(x,mod - 2);
}

int last[SZ];

int main()
{
    scanf("%d%d",&n,&m);
    init :: gen(100000);
    for(int i = 1,x;i <= n;i ++)
        scanf("%d",&x),init :: fj(x,i);
    for(int i = 1;i <= n;i ++)
        sum[i] = 1;
    for(int i = 1;i <= tot;i ++)
        a[i].last = last[a[i].num],last[a[i].num] = a[i].id;

    for(int i = 1;i <= m;i ++)
        scanf("%d%d",&ask[i].l,&ask[i].r),ask[i].id = i;
    sort(ask + 1,ask + 1 + m,cmp);
    for(int i = 1,j = 1,k = 1;i <= n;i ++)
    {
        while(j <= tot && a[j].id == i)
            mult(i,a[j].val),mult(a[j].last,ni(a[j].val)),j ++;
        while(k <= m && ask[k].r == i)
            ask[k].ans = (LL)ask_ans(ask[k].r) * ni(ask_ans(ask[k].l - 1)) % mod,k ++;
    }
    sort(ask + 1,ask + 1 + m,cmp2);
    for(int i = 1;i <= m;i ++)
        printf("%lld\n",ask[i].ans);
    return 0;
}

你可能感兴趣的:(【TsinsenA1339】JZPLCM(顾昱洲) 树状数组)