codeforces-1114F-線段樹練習

http://www.javashuo.com/article/p-pstmjjaj-q.htmlhtml

概述

這是一道用線段樹維區間值的一道題,,題意很簡單,,就是對於給定的一個序列,,初始時每一個數的值不大於300,,,而後有兩中操做,,一個是對區間[l, r]的每一個數乘上以個數x,,一個是詢問區間的乘積的歐拉函數值,,,ios

分析

首先對於第一個操做顯然能夠用線段樹的延遲更新來完成,,c++

對於第二個操做,,我最早沒考慮數據,,就想着直接維護區間的乘積,,對最後的區間乘積求歐拉函數值,,,可是,,,即便數據初始值很小,,可是屢次累乘x後會爆ll,甚至是ull,,,git

正解是這樣的:數組

對於第一個操做,,每次都保存區間模mod的乘積,,,函數

對於第二個操做,,由於咱們是求的區間積的歐拉函數值,也就是工具

\(MUL_{l,r} \times phi(Mul_{l, r}) = Mul_{l, r} \times \prod_{i=l}^j {prime[i]-1 \over prime[i]}\)ui

\(prime[i] 是指 Mul_{l, r} 的質因數\)spa

由於直接存 \(Mul_{l, r}\) 會爆掉,,而最後的結果實在mod下的數,,300以裏的質數也只有62個,,因此能夠標記出乘積的全部質因數,,用一個ll的數就好了(狀壓的思想),,對於任意一個區間的乘積的標記均可以用兩個子節點的標記值的或運算獲得,,同時標記值也只會由於乘上的那個數x而增長,,,公式裏的除 \(prime[i]\) 也能夠用逆元搞定,,這樣這個操做就弄出來了,,.net

一開始我本身寫爆了以後,就照着別人的思路一點一點的改,,莫名其妙的t,,一直覺得是線段樹寫醜了,,,,後來看到一我的寫的很簡單但也過了,,,本身就重寫了一遍過了,,數論+線段樹的題第一次寫,,學到不少,,尤爲是狀壓的思想,,逆元,還有線段樹做爲一個維護的工具的使用,,,兩個參數的返回可使用 pair<int, ll> pii 型來返回,,

代碼

#include <bits/stdc++.h>
//#include <iostream>
//#include <cstdio>
//#include <cstdlib>
//#include <string.h>
#define aaa cout<<233<<endl;
#define endl '\n'
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, ull> pii;
const int inf = 0x3f3f3f3f;//1061109567
const ll linf = 0x3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double pi = 3.14159265358979;
const int maxn = 4e5 + 5;   //注意數據範圍,,,由於這個wa了一發,,,,(爲啥不是re233)
const int maxm = 2e5 + 5;
const ll mod = 1e9 + 7;
inline int read()   //快讀
{
    int ans=0;
    char ch=getchar();
    while(!isdigit(ch))
        ch=getchar();
    while(isdigit(ch))
        ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
    return ans;
}
inline ll pow_(ll a, ll b, ll p)    //快速冪
{
    ll ret = 1;
    while(b)
    {
        if(b & 1) ret = (ret * a) % p;
        a = (a * a) % p;
        b >>= 1;
    }
    return ret;
}
//find all prime from 1 to 300
bool isprime[305];
int prime[70], tot = -1;
int inv_prime[70];
void init()     //尋找300之內的質數及其質數的逆元
{
    for(int i = 2; i <= 300; ++i)isprime[i] = false;
    for(int i = 2; i <= 300; ++i)
    {
        if(!isprime[i])prime[++tot] = i, inv_prime[tot] = pow_(i, mod - 2, mod);
        for(int j = 0; j <= tot && i * prime[j] <= 300; ++j)
        {
            isprime[i * prime[j]] = true;
            if(i % prime[j] == 0)break;
        }
    }
}
ll find_prime(ll x) //尋找數x的質因數,存在則對應質數數組的index位位1,這樣最後返回的值的二進制表示即爲狀壓標記的結果
{
    ull ret = 0;
    for(int i = 0; i <= tot; ++i)if(x == x / prime[i] * prime[i])ret |= ((ll)1 << i);
    return ret;
}
ll mull(ll a, ll b) //帶模的乘法
{
    return a * b % mod;
}
ll mul[maxn << 2], vis[maxn << 2], laz1[maxn << 2], laz2[maxn << 2];
int a[maxn];
#define mid ((l+r)>>1)
#define lc (rt<<1)
#define rc (rt<<1|1)
void pushup(int rt)
{
    mul[rt] = mull(mul[lc], mul[rc]);
    vis[rt] = vis[lc] | vis[rc];
    return;
}
void pushdown(int rt, int llen, int rlen)
{
    mul[lc] = mull(mul[lc], pow_(laz1[rt], llen, mod)); //更新乘積
    mul[rc] = mull(mul[rc], pow_(laz1[rt], rlen, mod));
    laz1[lc] = mull(laz1[lc], laz1[rt]);                //更新子區間乘積的懶惰標記值
    laz1[rc] = mull(laz1[rc], laz1[rt]);
    laz1[rt] = 1;                                       //恢復根區間乘積的懶惰標記值
    vis[lc] |= laz2[rt];                                //更新標記
    vis[rc] |= laz2[rt];
    laz2[lc] |= laz2[rt];                               //更新子區間標記的懶惰標記值
    laz2[rc] |= laz2[rt];
    laz2[rt] = 0;                                       //恢復根區間標記的懶惰標記值

    return;
}
inline void build(int rt, int l, int r)
{
    mul[rt] = vis[rt] = laz2[rt] = 0;
    laz1[rt] = 1;                       //無標記時,乘積的標記的懶惰值爲1,,,,,標記的爲0,,
    if(l == r)
    {
        mul[rt] = a[l];
        vis[rt] = find_prime(mul[rt]);  //葉子節點的標記值爲其質因數出現的狀壓後的值
        return;
    }
    build(lc, l, mid);
    build(rc, mid + 1, r);
    pushup(rt);
    return;
}
inline void update(int rt, int l, int r, int L, int R, int x, ll vx)
{
    if(L <= l && r <= R)
    {
        mul[rt] = mull(mul[rt], pow_(x, r - l + 1, mod));
        vis[rt] |= vx;                      //標記更新
        laz1[rt] = mull(laz1[rt], x);       //乘積的懶惰標記的更新
        laz2[rt] |= vx;                     //標記的懶惰標記的更新
        return;
    }
    if(laz1[rt] > 1)pushdown(rt, mid - l + 1, r - mid);
    if(laz2[rt])pushdown(rt, mid - l + 1, r - mid);
    if(R <= mid)update(lc, l, mid, L, R, x, vx);
    else if(L > mid)update(rc, mid+1, r, L, R, x, vx);
    else update(lc, l, mid, L, R, x, vx), update(rc, mid+1, r, L, R, x, vx);
//    if(L <= mid)update(lc, l, mid, L, R, x, vx);
//    if(R > mid)update(rc, mid + 1, r, L, R, x, vx);
    pushup(rt);
    return;
}
inline pii query(int rt, int l, int r, int L, int R)//詢問區間的乘積值和標記值
{
    if(L <= l && r <= R)
    {
        return pii(mul[rt], vis[rt]);
    }
    if(laz1[rt] > 1)pushdown(rt, mid - l + 1, r - mid);//乘積的懶惰標記大於一說明待更新區間
    if(laz2[rt])pushdown(rt, mid - l + 1, r - mid); //標記的懶惰值非零說明待更新
    if(R <= mid)return query(lc, l, mid, L, R);     //詢問區間再左子區間時,,遞歸詢問左子區間
    if(L >  mid)return query(rc, mid + 1, r, L, R);
    pii a = query(lc, l, mid, L, R);                //a爲佐子區間的值
    pii b = query(rc, mid + 1, r, L, R);            //b爲侑子區間的值
    return pii(mull(a.first, b.first), (a.second | b.second));//總區間的值爲左右子區間的乘積的積和標記的或
}
ll phi(ll mul, ull vis) //利用標記指求其歐拉函數值
{
    for(int i = 0; i <= tot; ++i)
        if((vis >> i) & 1)
            mul = mull(mul, mull(prime[i] - 1, inv_prime[i]));
    return mul;
}
int main()
{
//    freopen("233.in" , "r" , stdin);
//    freopen("233.out" , "w" , stdout);
//    ios_base::sync_with_stdio(0);
//    cin.tie(0);cout.tie(0);
    int n, q;
    //n = read(); q = read();
    scanf("%d%d", &n, &q);
    for(int i = 1; i <= n; ++i)a[i] = read();
    init();     //初始化找出300之內的全部素數,和對應的逆元
    build(1, 1, n); //建樹
    char s[20];
    int l, r, x;

    while(q--)
    {
        scanf("%s", s);
        l = read();r = read();
        if(s[0] == 'M')
        {
            //l = read(); r = read(); x = read();
            //scanf("%d", &x);
            x = read();
            update(1, 1, n, l, r, x, find_prime(x));//更新操做,最後一個參數是x的質因數的標記值
        }
        else
        {
            //l = read(); r = read();
            pii tmp = query(1, 1, n, l, r); //返回區間值的乘積和他的標記
//            cout << tmp.first << "---" << tmp.second << endl;
//            ll ans = 1;
//            for(int i = l; i <= r; ++i)ans = mull(ans, query(1, i, i).first);
//            cout << ans << endl;
            printf("%lld\n", phi(tmp.first, tmp.second));
        }
    }

    return 0;
}

感想

看來只作簡單題是學不到新東西的,,,難題雖然難,,熬夜弄了兩天wa了好幾發但最後弄出來仍是頗有意義的,,,

同時多看看別人的代碼也頗有感觸,,學到不少好東西,,

(end)

相關文章
相關標籤/搜索