BZOJ 3813 奇数国 欧拉函数+线段树+乘法逆元

题目大意:给出一个序列,支持修改操作,求这个序列连续一段的乘积的欧拉函数。每个数的最大质因子不超过281。


思路:φ(n) = n * (1 - 1 / p1) * (1 - 1 / p2) * (1 - 1 / p3) * (1 - 1 / p4)……*(1 - 1 / pn)

                 = n  / (p1 * p2 * p3 * …… * pn) * ((p1 - 1) * (p2 - 1) * (p3 - 1) * …… * (pn - 1))

于是这个东西只需要维护一下区间中的指数,用bitset维护一下质因数,出发就用一下乘法逆元,没了。。


CODE:

#include <bitset>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define P 19961993
#define MAX 100010
using namespace std;
#define LEFT (pos << 1)
#define RIGHT (pos << 1|1)
#define max(a,b) ((a) > (b) ? (a):(b))
  
struct Ask{
    int flag,x,y;
      
    void Read() {
        scanf("%d%d%d",&flag,&x,&y);
    }
}ask[MAX];
  
struct SegTree{
    bitset<61> prime;
    int total;
      
    SegTree(bitset<61> _,int __):prime(_),total(__) {}
    SegTree() {}
    SegTree operator +(const SegTree &a)const {
        return SegTree(prime|a.prime,(long long)total * a.total % P);
    }
}tree[MAX << 2];
  
int G[300],g[300];
long long inv[300];
int asks,cnt;
  
inline bool Judge(int x)
{
    for(int i = 2; i * i <= x; ++i)
        if(x % i == 0)
            return false;
    return true;
}
  
void Shake()
{
    inv[1] = 1;
    for(int i = 2; i <= 281; ++i)
        inv[i] = (P - P / i) * inv[P % i] % P;
}
  
void Pretreatment()
{
    Shake();
    int cnt = 0;
    for(int i = 2; i <= 281; ++i)
        if(Judge(i))
            G[i] = ++cnt,g[cnt] = i;
}
  
void BuildTree(int l,int r,int pos)
{
    if(l == r) {
        tree[pos].prime[G[3]] = 1;
        tree[pos].total = 3;
        return ;
    }
    int mid = (l + r) >> 1;
    BuildTree(l,mid,LEFT);
    BuildTree(mid + 1,r,RIGHT);
    tree[pos] = tree[LEFT] + tree[RIGHT]; 
}
  
SegTree Ask(int l,int r,int x,int y,int pos)
{
    if(l == x && r == y)    return tree[pos];
    int mid = (l + r) >> 1;
    if(y <= mid) return Ask(l,mid,x,y,LEFT);
    if(x > mid)      return Ask(mid + 1,r,x,y,RIGHT);
    SegTree left = Ask(l,mid,x,mid,LEFT);
    SegTree right = Ask(mid + 1,r,mid + 1,y,RIGHT);
    return left + right;
}
  
void Modify(int l,int r,int x,int pos,SegTree c)
{
    if(l == r) {
        tree[pos] = c;
        return ;
    }
    int mid = (l + r) >> 1;
    if(x <= mid) Modify(l,mid,x,LEFT,c);
    else    Modify(mid + 1,r,x,RIGHT,c);
    tree[pos] = tree[LEFT] + tree[RIGHT];
}
  
int main()
{
    Pretreatment();
    cin >> asks;
    for(int i = 1; i <= asks; ++i)
        ask[i].Read();
    cnt = MAX - 1;
    BuildTree(1,cnt,1);
    for(int flag,x,y,i = 1; i <= asks; ++i) {
        flag = ask[i].flag;
        x = ask[i].x,y = ask[i].y;
        if(flag) {
            bitset<61> p;
            for(int j = 1; j <= 60; ++j)
                if(y % g[j] == 0)
                    p[j] = 1;
            SegTree c(p,y);
            Modify(1,cnt,x,1,c);
        }
        else {
            SegTree ans = Ask(1,cnt,x,y,1);
            for(int j = 1; j <= 60; ++j)
                if(ans.prime[j]) {
                    ans.total = ((long long)ans.total * inv[g[j]]) % P;
                    ans.total = ((long long)ans.total * (g[j] - 1)) % P;
                }
            printf("%d\n",ans.total);
        }
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值