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;
}

 

posted on 2015-03-03 23:35  Vking不说话  阅读(526)  评论(0编辑  收藏  举报

导航