REQ 【CodeForces - 594D】【树状数组+离线查询+区间思维】

题目链接


  很好的一道题,昨晚上推的,今天由于代码能力太弱敲了半天,再不断的找到自己思维的BUG,于是RE了一发、T了一发、WA了一发,就Ac了,还不错,那我们来讲解一下题目的思路。

  我们知道对于一个值的欧拉函数值,就是它的值去乘上它所有的质数-1除以质数:如果设某数的质数为p,那么其欧拉值就等于原值*(p-1)/p,其中,我们的“/p”操作得用逆元来解,所以剩下的就是怎么处理保存区间中不同种类的质数们了,所以,遇上这种问题,并且还是没有更新的,我就想到了把查询离线的做法。

  知道每个数的质数,我们先预处理一下,对于所有1e6以内的数,我们先处理所有的质数,用质数筛法可以做到,然后,我们预处理的方式来把他们的质因子们放进一个vector<>存放的STL中去,然后逐一把里面的值给求出来。

void init()     //把1~1000000每个数的质因子写出来
{
    memset(Prime, 0, sizeof(Prime));    //0是质数、1非质数(合数)
    Prime[0] = Prime[1] = 1;
    cnt = 0;
    for(int i=2; i<maxV; i++)
    {
        if(Prime[i] == 0)
        {
            for(int j=2*i; j<maxV; j+=i)
            {
                Prime[j] = 1;
            }
            sushu[++cnt] = i;
        }
    }
    for(int i=1; i<maxV; i++) have_p[i].clear();
    for(int i=2; i<maxV; i++) get_have(i, i);
}

  其中,get_have()是用来得到每个数对应的质因子的函数:

void get_have(int index, int v)
{
    int i=1;
    while(v>1 && i<=cnt)
    {
        if(sushu[i] * sushu[i] > v)
        {
            have_p[index].push_back(v);
            return;
        }
        if(v%sushu[i] == 0)
        {
            have_p[index].push_back(sushu[i]);
            while(v%sushu[i] == 0) v/=sushu[i];
        }
        i++;
    }
}

  然后,我们既然已经处理完了质因子了,我们接下来只需要处理每个质因子前后出现的位置就行了,为什么要这么处理?我们查询的是一个区间,其中可能会有一串相等的质因子,我们得处理它们,所以,我们的查询就有了目的,我们得预处理每个质因子上次出现的位置,以及下次出现的位置,对于查询区间的右移,我们只需要把目前节点的出现的质因子用后面位置的质因子代替就行,譬如,现在的区间是[ql, qr],我们的下一个查询区间是[ql+1, qr],那么ql的点就是区间外的点了(我对查询区间按照左端点升序排序),那么这个点旗下有些它的质因子,我们把它的质因子向后推就是了,如果之后的区间并没有出现这样的质因子,我们将它的r[][](也就是下一次出现的位置,第i个位置的点,它的第j个质因子)放到(N+1)的位置上去,同理,对于第一个点也是这样的处理方式。

  这么就有了l、r的思想了,l记录前缀,如果前缀是0,表明它是第一次出现,如果后缀是N+1,表明它之后就没有这个质数的参与了,所以,我们列写下预处理的函数:

void init2()
{
    for(int i=1; i<=N; i++) trie[i] = 1;
    memset(l, 0, sizeof(l));
    int flag[maxV]; memset(flag, 0, sizeof(flag));
    for(int i=1; i<=N; i++)
    {
        int len = (int)have_p[a[i]].size();
        for(int j=0; j<len; j++)
        {
            l[i][j] = flag[have_p[a[i]][j]];
            flag[have_p[a[i]][j]] = i;
        }
    }
    for(int i=1; i<maxV; i++) flag[i] = N + 1;
    for(int i=N; i>=1; i--)
    {
        int len = (int)have_p[a[i]].size();
        for(int j=0; j<len; j++)
        {
            r[i][j] = flag[have_p[a[i]][j]];
            flag[have_p[a[i]][j]] = i;
        }
    }
}

  剩下的区间查找就显得很简单了,我们的树状数组记录的是前缀积,写法也有所不同,记录每个质因子的时候,我们存进去的是(p-1)/p的值,“/p”用的是逆元。然后,原数的前缀积我们就直接用前缀和的思想了,当然除的时候还是用逆元的思想。

  对了,还有人会问我,为什么可以把质因子存起来,这么就很简单了,2*3*5*7*11*13*17*19=9699690(9e6),也就是说,1e6内的数最多的质因子种类数就是8个,所以,复杂度足够。


#pragma comment(linker, "/STACK:102400000,102400000")
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int mod = 1e9+7;
const int maxN = 2e5+5;
const int maxV = 1e6+2;
int N, Q, Prime[maxV], sushu[maxV], cnt;    //sushu[]记录质素们,cnt记录到200000一共有多少个素数
ll a[maxN], trie[maxN], pre[maxN], ans[maxN];
vector<ll> have_p[maxV];
int l[maxN][10], r[maxN][10];   //对应点i的第j个质因子左、右分别出现位置,没有就是0、(N+1)
struct Ques     //离线查询
{
    int l, r, id;
    Ques(int a=0, int b=0, int c=0):l(a), r(b), id(c) {}
}qes[maxN];
bool cmp(Ques e1, Ques e2) { return e1.l < e2.l; }
ll fast_mi(ll x, int y)     //快速幂处理逆元
{
    ll ans = 1;
    while(y)
    {
        if(y & 1) ans = ans*x%mod;
        x = x*x%mod;
        y>>=1;
    }
    return ans;
}
void update(int x, ll val)
{
    if(x == 0) return;
    while(x<=N)
    {
        trie[x] = trie[x]*val%mod;
        x += lowbit(x);
    }
}
ll query(int x)
{
    ll ans = 1;
    while(x>0)
    {
        ans = ans*trie[x]%mod;
        x -= lowbit(x);
    }
    return ans;
}
void get_have(int index, int v)
{
    int i=1;
    while(v>1 && i<=cnt)
    {
        if(sushu[i] * sushu[i] > v)
        {
            have_p[index].push_back(v);
            return;
        }
        if(v%sushu[i] == 0)
        {
            have_p[index].push_back(sushu[i]);
            while(v%sushu[i] == 0) v/=sushu[i];
        }
        i++;
    }
}
void init()     //把1~1000000每个数的质因子写出来
{
    memset(Prime, 0, sizeof(Prime));    //0是质数、1非质数(合数)
    Prime[0] = Prime[1] = 1;
    cnt = 0;
    for(int i=2; i<maxV; i++)
    {
        if(Prime[i] == 0)
        {
            for(int j=2*i; j<maxV; j+=i)
            {
                Prime[j] = 1;
            }
            sushu[++cnt] = i;
        }
    }
    for(int i=1; i<maxV; i++) have_p[i].clear();
    for(int i=2; i<maxV; i++) get_have(i, i);
}
void init2()
{
    for(int i=1; i<=N; i++) trie[i] = 1;
    memset(l, 0, sizeof(l));
    int flag[maxV]; memset(flag, 0, sizeof(flag));
    for(int i=1; i<=N; i++)
    {
        int len = (int)have_p[a[i]].size();
        for(int j=0; j<len; j++)
        {
            l[i][j] = flag[have_p[a[i]][j]];
            flag[have_p[a[i]][j]] = i;
        }
    }
    for(int i=1; i<maxV; i++) flag[i] = N + 1;
    for(int i=N; i>=1; i--)
    {
        int len = (int)have_p[a[i]].size();
        for(int j=0; j<len; j++)
        {
            r[i][j] = flag[have_p[a[i]][j]];
            flag[have_p[a[i]][j]] = i;
        }
    }
}
int main()
{
    init();
    while(scanf("%d", &N)!=EOF)
    {
        pre[0] = 1;
        for(int i=1; i<=N; i++) { scanf("%lld", &a[i]); pre[i] = pre[i-1] * a[i]%mod; }     //前缀积处理
        scanf("%d", &Q);
        for(int i=1; i<=Q; i++) { scanf("%d%d", &qes[i].l, &qes[i].r); qes[i].id = i; }
        sort(qes+1, qes+1+Q, cmp);
        init2();
        int i = 1, j = 1;
        for(int q=1; q<=Q; q++)
        {
            while(j<=qes[q].r)
            {
                int len = (int)have_p[a[j]].size();
                for(int k=0; k<len; k++) if(l[j][k] == 0) update(j, (have_p[a[j]][k] - 1)*fast_mi(have_p[a[j]][k], mod-2)%mod);
                j++;
            }
            while(i<qes[q].l)
            {
                int len = (int)have_p[a[i]].size();
                for(int k=0; k<len; k++) update(r[i][k], (have_p[a[i]][k] - 1)*fast_mi(have_p[a[i]][k], mod-2)%mod);
                i++;
            }
            ans[qes[q].id] = pre[qes[q].r]*fast_mi(pre[qes[q].l-1], mod-2)%mod*query(qes[q].r)%mod*fast_mi(query(qes[q].l-1), mod-2)%mod;
        }
        for(int i=1; i<=Q; i++) printf("%lld\n", ans[i]);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41730082/article/details/84796167
今日推荐