HDU5152 线段树 + 数论

  题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5152 ,线段树区间更新 + 点更新 + 数论知识(数论是重点QAQ),好题值得一做。

  BestCoder Round #24的C题,一道神题,不得不说,出题人的数论学的很好,很多人都没想到2333333不是素数的问题,当时全场爆零。我今天下午开始研究这道题,后来看了好久的标程才懂,惭愧。



  一共有两个操作一个询问:1.询问[l , r]区间里的值的和; 2.将点x的值a[x]赋为2a[x]; 3.将区间[l , r]都加上x。

  这道题复杂就是操作2了,这里需要先知道一个数论知识:  当x >= Phi(C)时, A^x = A ^ (x%Phi(C) + Phi(C)) (mod C). Phi(C)是C的欧拉函数,即1 ~ C中与C互素整数的个数,具体求法百度之。所以当操作2一直累积下去的时候应该是这样:

           

  所以就是一直迭代求2333333的欧拉函数,对于2333333这个模数来说,求18次欧拉函数后就变成了1,所以保存到19层即可。接下来就是线段树的更新与求和。

具体解法:

  这里只介绍操作2的部分:标程中用了一个一维的vector向量来保存两个信息:每个位置的操作2的次数和操作3要加的数,具体实现方法是vector <LL> a[N]后,如果i号位置需要进行操作2,则进行操作a[i].pushback(0),如果操作3的PushDown()更新到i的位置时,则a[i][a[i].size() - 1] += add[rt]; 表示在a[i][]数组的最后一个位置加上要加的数。这样的话a[i][]数组的长度就表示有多少次操作2,保存的值就代表了当时的操作3加上的值,所以每次迭代应该是num = 2num % phi[pos] + phi[pos] + a[...] (说点有点乱,不好意思~)。

  还有一点要注意的是x < Phi(C)的情况,这样的话A ^ x 还等于 A ^ x,这样的话迭代就变为 num = 2num + a[...]. 这时候再判断num与当前层的欧拉函数的大小关系,到满足条件时再像公式中的进行即可。这里要注意一点,如果当前层满足 x >= Phi(C)的情况,则以后的每一层迭代结果必然也满足,因为 x % Phi(C) + Phi(C) 必然要大于Phi(C),这样传递下去就可以保证以后的都满足了...(- - ..还是说的很乱,具体见代码,虽然都是看的标程编的QAQ)。

#include <iostream>
#include <cstdio>
#include <vector>
#include <cmath>
#include <string>
#include <string.h>
#include <algorithm>
using namespace std;
#define LL __int64
#define eps 1e-8
#define INF INT_MAX
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
const int MOD = 2333333;
const int maxn = 50000 + 5;
int phi[20] = {2333333 , 2196720 , 580608 , 165888 , 55296 , 18432 , 6144 ,
            2048 , 1024 , 512 , 256 , 128 , 64 , 32 , 16 , 8 , 4 , 2 , 1};
LL sum[maxn << 2] , add[maxn << 2];
vector <LL> a[maxn];
int pow2[33];

void init()
{                //求2 ^ i
    for(int i = 0 ; i <= 30 ; i++)
        pow2[i] = 1 << i;
}
LL pow_mod(LL a , LL i , LL n)
{                // a ^ i % n的快速幂
    if(i == 0)
        return 1;
    LL tmp = pow_mod(a , i >> 1 , n);
    tmp = tmp * tmp % n;
    if(i & 1)
        tmp = tmp * a % n;
    return tmp;
}
void PushUp(int rt)
{
    sum[rt] = (sum[rt << 1] + sum[rt << 1 | 1]) % MOD;
}
void build(int l , int r , int rt)
{
    add[rt] = 0;
    if(l == r) {
        a[l].clear();                //清零不要忘了
        scanf("%d" , &sum[rt]);
        a[l].push_back(sum[rt]);    //初始值放入a[l][0]
        sum[rt] %= MOD;
        return;
    }
    int m = (l + r) >> 1;
    build(lson);
    build(rson);
    PushUp(rt);
}
void PushDown(int rt , int len)
{
    if(add[rt]) {
        add[rt << 1] += add[rt];
        add[rt << 1 | 1] += add[rt];
        sum[rt << 1] = (sum[rt << 1] + 1LL * (len - (len >> 1)) * add[rt]) % MOD;
        sum[rt << 1 | 1] = (sum[rt << 1 | 1] + 1LL * (len >> 1) * add[rt]) % MOD;
        add[rt] = 0;
    }
}
void update(int L , int R , int x , int l , int r , int rt)
{
    if(L <= l && R >= r) {
        sum[rt] = (sum[rt] + 1LL * (r - l + 1) * x) % MOD;
        add[rt] += x;
        return;
    }
    PushDown(rt , r - l + 1);
    int m = (l + r) >> 1;
    if(L > m)
        update(L , R , x , rson);
    else if(R <= m)
        update(L , R , x , lson);
    else {
        update(L , R , x , lson);
        update(L , R , x , rson);
    }
    PushUp(rt);
}
int cal(vector <LL> a)
{
    LL num;
    if(a.size() < 19) {        //没到18层,所以要全部来一遍
        num = a[0];
        bool flag = false;    //flag判断是否满足 x >= Phi(C)
        int pos = a.size() - 1;
        if(num >= phi[pos]) {
            flag = true;
            num = num % phi[pos] + phi[pos];
        }
        pos--;
        for(int i = 1 ; i < a.size(); i++ , pos--) {
            if(flag) {
                num = (pow_mod(2 , num , phi[pos]) + a[i]) % phi[pos] + phi[pos];
            } else {
                if(num >= 30) {
                    flag = true;
                    num = (pow_mod(2 , num , phi[pos]) + a[i]) % phi[pos] + phi[pos];
                } else {
                    num = pow2[num] + a[i];        //这时就是 2 ^ num + a[i]
                    if(num >= phi[pos]) {
                        flag = true;
                        num = num % phi[pos] + phi[pos];
                    }
                }
            }
        }
    } else {        //由于Phi[18]就等于1了,所以之前取余就都等于0,不管就可以了
        num = 1;
        int pos = 17;
        for(int i = a.size() - 18 ; i < a.size() ; i++ , pos--) {
            num = (pow_mod(2 , num , phi[pos]) + a[i]) % phi[pos] + phi[pos];
        }
    }
    return num % MOD;
}
void modify(int p , int l , int r , int rt)
{
    if(l == r) {
        if(add[rt]) {    //保留操作3的信息
            a[p][a[p].size() - 1] += add[rt];
            add[rt] = 0;
        }
        a[p].push_back(0);    //加一层
        sum[rt] = cal(a[p]);
        return;
    }
    PushDown(rt , r - l + 1);
    int m = (l + r) >> 1;
    if(p <= m)
        modify(p , lson);
    else
        modify(p , rson);
    PushUp(rt);
}
int query(int L , int R , int l , int r , int rt)
{
    if(L <= l && R >= r) {
        return sum[rt] % MOD;
    }
    PushDown(rt , r - l + 1);
    int m = (l + r) >> 1;
    if(L > m)
        return query(L , R , rson);
    else if(R <= m)
        return query(L , R , lson);
    else
        return (query(L , R , lson) + query(L , R , rson)) % MOD;
}
int main()
{
    init();
    int a , b , c , n , m , ch;
    while(~scanf("%d %d" , &n , &m))
    {
        build(1 , n , 1);
        while(m--) {
            scanf("%d" , &ch);
            if(ch == 1) {
                scanf("%d %d" , &a , &b);
                printf("%d\n" , query(a , b , 1 , n , 1));
            } else if(ch == 2) {
                scanf("%d" , &c);
                modify(c , 1 , n , 1);
            } else {
                scanf("%d %d %d" , &a , &b , &c);
                update(a , b , c , 1 , n , 1);
            }
        }
    }
    return 0;
}

另附欧拉函数的代码:

int Euler(int n)
{
    vector <int> a;
    int i = 2;
    int res = n;
    while(n != 1) {
        if(n % i == 0)
            a.push_back(i);
        while(n % i == 0)
            n /= i;
        i++;
    }
    for(i = 0 ; i < a.size() ; i++)
        res = res / a[i] * (a[i] - 1);
    return res;
}
时间: 2024-10-09 05:40:59

HDU5152 线段树 + 数论的相关文章

RGCDQ(线段树+数论)

题意:求n和m之间的所有数的素因子个数的最大gcd值. 分析:这题好恶心,看着就是一颗线段树,但本题有一定的规律,我也是后来才发现,我还没推出这个规律,就不说了,就用纯线段树解答吧.因为个点数都小于1000000所以素因子个数不会超过7个所以建一个线段树,最下面一层是每个节点的素因子个数为1,2,3,4,5,6,7的有多少个,父节点求和,最终查询的是n到m之间有多少个1,2,3,4,5,6,7然后存在就求一下gcd着最大就好了 本题最重要的时间和空间,显然线段数中的点不会很大,所以采用short

HDU 5239 上海大都会 D题(线段树+数论)

打表,发现规律是存在一定次数(较小)后,会出现a=(a*a)%p.可以明显地发现本题与线段树有关.设置标记flag,记录本段内的数是否均已a=a*a%p.若是,则不需更新,否则更新有叶子结点,再pushup. #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #define LL unsigned long long using namespace st

BZOJ 3813 奇数国 线段树+数论

题目大意:给定一个序列,每个数都由60个最小的素数的乘积构成,求某段的乘积的欧拉函数值对19961993取模后的值,支持单点修改 19961993是个质数 出题人还是满贴心的 利用线段树维护乘积取模后的值以及哪些素数出现过 后者用bitset维护 得到的值根据bitset里出现过的素数来计算欧拉函数值 时间复杂度O(nlog10W+60n) #include <bitset> #include <cstdio> #include <cstring> #include &

poj---(2886)Who Gets the Most Candies?(线段树+数论)

Who Gets the Most Candies? Time Limit: 5000MS   Memory Limit: 131072K Total Submissions: 10373   Accepted: 3224 Case Time Limit: 2000MS Description N children are sitting in a circle to play a game. The children are numbered from 1 to N in clockwise

codeforces 446C DZY Loves Fibonacci Numbers(数学 or 数论+线段树)

In mathematical terms, the sequence Fn of Fibonacci numbers is defined by the recurrence relation F1 = 1; F2 = 1; Fn = Fn - 1 + Fn - 2 (n > 2). DZY loves Fibonacci numbers very much. Today DZY gives you an array consisting of n integers: a1, a2, ...,

codeforces 446C DZY Loves Fibonacci Numbers 数论+线段树成段更新

DZY Loves Fibonacci Numbers Time Limit:4000MS     Memory Limit:262144KB     64bit IO Format:%I64d & %I64u Submit Status Appoint description:  System Crawler  (2014-07-14) Description In mathematical terms, the sequence Fn of Fibonacci numbers is defi

【暑期集训第一场】欧拉回路 | 思维 | 数论构造 | 容斥原理 | 线段树 | 归并排序

集训1(HDU2018 Multi-University Training Contest 2) ID A B C D E F G H I J AC O O 补题 ? O ? O 代码 & 简易题解 [A]:期望? 神仙题,留坑.. [B]:?? 同\(\text{A}\) [C]:求欧拉通路条数,以及每条的路径 小学数竞里有讲过,无向图一笔画的充要条件是有零个或两个"奇点"(偶点个数不限),"奇点"在这里就是指度为奇数的点... 其实上面两种情况就分别对应

Bash and a Tough Math Puzzle CodeForces 914D 线段树+gcd数论

Bash and a Tough Math Puzzle CodeForces 914D 线段树+gcd数论 题意 给你一段数,然后小明去猜某一区间内的gcd,这里不一定是准确值,如果在这个区间内改变一个数的值(注意不是真的改变),使得这个区间的gcd是小明所猜的数也算小明猜对.另一种操作就是真的修改某一点的值. 解题思路 这里我们使用线段树,维护区间内的gcd,判断的时候需要判断这个区间的左右子区间的gcd是不是小明猜的数的倍数或者就是小明猜的数,如果是,那么小明猜对了.否则就需要进入这个区间

CF1114F Please, another Queries on Array?(线段树,数论,欧拉函数,状态压缩)

这题我在考场上也是想出了正解的……但是没调出来. 题目链接:CF原网 题目大意:给一个长度为 $n$ 的序列 $a$,$q$ 个操作:区间乘 $x$,求区间乘积的欧拉函数模 $10^9+7$ 的值. $1\le n\le 4\times 10^5,1\le q\le 2\times 10^5,1\le a_i,x\le 300$.时限 5.5s,空限 256MB. 明显线段树. 有一个想法是维护区间积的欧拉函数,但是这样时间复杂度和代码复杂度都很高…… 我的做法是维护区间积.而欧拉函数,就是看看