F. Please, another Queries on Array? (线段树维护区间积的欧拉函数)

传送门

在这里插入图片描述

给定序列,两种操作,区间每个数乘v,求区间积(对1e9+7取模)后的欧拉函数。

序列每个数以及v都不超过300。

  • 首先bd得到欧拉函数的计算公式:
    在这里插入图片描述
  • 首先序列每个数不超过300,v也不超过300,300以内的质数很少,打表发现只有62个,这个信息显然很有用。
  • 区间欧拉函数就是区间积,很明显可以用线段树维护 。把欧拉函数看成两部分相乘,一个是x本身,一个是x包含的所有质因子项的乘积,我们线段树维护的节点有两个信息,一个是区间乘积,一个是区间包含的质因子,后者用状压,用一个longlong的变量保存。
  • 质因子项我们可以预处理+逆元得到,用f[i]表示第i个质数p[i]的 (p[i]-1)/p[i] % mod
  • 能否不存质因子,最后求出区间乘积之后再求质因子呢?不行,因为x在乘完之后要取模,取模后求的质因子肯定是错了的,所以我们维护区间积的同时,再维护区间所有数的质因子。

区间合并:
在这里插入图片描述

其余细节见注释。

#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define ll long long
#define ull unsigned long long
#define pll pair<ll,ll>
#define re register
#define lc rt<<1
#define rc rt<<1|1
const int maxn = 4e5+10;
const int mx = 40;
const ll mod = 1e9+7;
const ll inf = 34359738370;
const ll INF = 1e9+7;
const double pi = acos(-1.0);
//给定序列 初始值不超过300  区间乘 查询区间积的欧拉函数
//维护区间积以及区间积的质因子状态
//用线段树 欧拉函数根据公式计算 由于涉及取模 需要记录质因子 预处理每个质因子乘积项
ll tree[maxn<<2],has[maxn<<2];//区间积  区间质因子状态
ll tag1[maxn<<2],tag2[maxn<<2];//乘积的懒标记  乘积质因子懒标记
ll sta[305];//数i的质因子压缩状态 右边第一位是第一个质数2
int pri[65],tot=0;//质数表
bool vis[305];
ll f[65];//预处理质因子项
ll inv[305];//逆元
void pre()
{
    for(int i=2;i<=300;i++) 
    {
        if(!vis[i]) pri[++tot]=i;
        for(int j=1;j<=tot && i*pri[j]<=300;j++) 
        {
            vis[i*pri[j]]=1;
            if(i % pri[j] == 0) break;
        }
    }
    for(int i=2;i<=300;i++) 
    {
        //i的每个质因子都压缩成二进制状态  右边第一个位置对应质数2
        for(int j=1;j<=tot;j++) 
        {
            if(i % pri[j] == 0) sta[i] |= 1ll<<(j-1);//注意是1ll 
        }
    }
    inv[1]=1;
    for(int i=2;i<=300;i++) 
        inv[i]=mod-1ll*(mod/i)*inv[mod%i]%mod;
    for(int i=1;i<=tot;i++) 
        f[i]=inv[pri[i]]*(pri[i]-1)%mod;//预处理 方便计算欧拉函数
}
inline void pushup(int rt) //合并区间信息
{
    tree[rt]=tree[lc]*tree[rc]%mod;
    has[rt]=has[lc] | has[rc];
}
void build(int rt,int l,int r) 
{
    tag1[rt]=1;//乘标记
    tag2[rt]=0;//质因子标记  这个区间乘的数包括哪些质因子 状压
    if(l == r) 
    {
        scanf("%d",&tree[rt]);
        has[rt]=sta[tree[rt]];
        return ;
    }
    int mid=l+r>>1;
    build(lc,l,mid);
    build(rc,mid+1,r);
    pushup(rt);
}
inline ll qpow(ll a,int b) 
{
    ll ans=1;
    for(; b ; b>>=1 , a=a*a%mod )  
        if(b&1) ans=ans*a%mod; 
    return ans;
}
inline void change(int rt,int len,ll v,ll y) //区间每个数乘v 更新区间乘积以及质因子状态
{
    tree[rt]=qpow(v,len)*tree[rt]%mod;
    has[rt] |= y;

    tag1[rt]=tag1[rt]*v%mod;
    tag2[rt] |= y;
}
inline void pushdown(int rt,int l,int r) 
{
    if(!tag2[rt]) return ;//这个区间没有乘过
    int mid=l+r>>1;
    change(lc,mid-l+1,tag1[rt],tag2[rt]);
    change(rc,r-mid,tag1[rt],tag2[rt]);
    //下传完 标记初始化
    tag1[rt]=1;
    tag2[rt]=0;
}
inline void updata(int rt,int l,int r,int v,int vl,int vr)//[vl,vr]每个数乘v 
{
    if(vl > r || vr < l ) return ;
    if(vl<=l && r<=vr) 
    {
        change(rt,r-l+1,v,sta[v]);
        return ;
    }
    int mid=l+r>>1;
    pushdown(rt,l,r);
    updata(lc,l,mid,v,vl,vr);
    updata(rc,mid+1,r,v,vl,vr);
    pushup(rt);
}
inline pll hb(pll a ,pll b) 
{
    a.first=a.first*b.first%mod;
    a.second |= b.second;
    return a;
}
//返回[vl,vr]区间积以及质因子状态 
inline pll query(int rt,int l,int r,int vl,int vr) 
{
    if(vl<=l && r<=vr) 
    {
        return make_pair(tree[rt],has[rt]);
    }
    int mid=l+r>>1;
    pushdown(rt,l,r);
    if(vr <= mid) return query(lc,l,mid,vl,vr);
    else if(vl > mid) return query(rc,mid+1,r,vl,vr);
    return hb(query(lc,l,mid,vl,vr),query(rc,mid+1,r,vl,vr));
}
int n,m;
int main()
{
    pre();//预处理别忘了写
    scanf("%d %d",&n,&m);
    build(1,1,n);
    char op[15];
    int l,r,v;
    while(m--) 
    {
        scanf("%s %d %d",op,&l,&r);
        if(op[0] == 'T') 
        {
            pll t=query(1,1,n,l,r);
            for(int i=1;i<=tot;i++) 
            {
                if(t.second & (1ll<<(i-1)))
                    t.first = (t.first*f[i])%mod;
            }
            printf("%lld\n",t.first);
        }
        else 
        {
            scanf("%d",&v);
            updata(1,1,n,v,l,r);
        }
    }
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值