bzoj4105: [Thu Summer Camp 2015]平方运算 线段树处理一类循环问题

bzoj4105: [Thu Summer Camp 2015]平方运算

Description

Input

第一行有三个整数N,M,p,分别代表序列的长度、平方操作与询问操作的总次数以及在平方操作中所要模的数。
接下来一行N个数代表一开始的序列{X1,X2,…,XN}。
接下来M行,每行三个整数op,l,r。其中op代表本次操作的类型。若op=0,代表这是一次平方操作,平方的区间为[l,r];如果op=1,代表这是一次询问操作,询问的区间为[l,r]。

Output

对于每次的询问操作,输出一行代表这段区间内数的总和。注意:答案没有对任何数取模。

Sample Input

3 3 11
1 2 3
1 1 3
0 1 3
1 1 3

Sample Output

6
14

HINT

对于100%的数据,∀i,Xi∈[0,p),l,r∈[1,n]
N,M,p的范围如下:
编号 N M p
1 1000 1000 233
2 1000 1000 2332
3 100000 100000 5
4 100000 100000 8192
5 100000 100000 23
6 100000 100000 45
7 100000 100000 37
8 55000 55000 4185
9 55000 55000 5850
10 55000 55000 2975
11 55000 55000 2542
12 55000 55000 2015
13 60000 60000 2003
14 65000 65000 2010
15 70000 70000 4593
16 75000 75000 4562
17 80000 80000 1034
18 85000 85000 5831
19 90000 90000 9905
20 100000 100000 9977

分析

有一个结论,就是这道题里的p的循环不会超过60(你没有发现p是定的么)
注意到这个循环是指这个队列的循环,也就是所有平方数循环的lcm不超过60,于是我们可以提前预处理出所有数的循环,然后用线段树维护循环。

知识点:线段树处理一类循环问题

首先定义几个数组

  1. in[i]:当前区间所有数是否都进入循环
  2. pos[i]:当前数位于循环的位置(进入循环前为0)。
  3. len[i]:本循环节长度
  4. val[i]:当前数的值
  5. C[i][j]:第i个数的循环
Step1:预处理

首先我们处理出所有循环节,并标记所有位于循环节内的数。

//inc[i]表示i这个数是否在循环节内,Move(i)表示将这个数按照题目要求转移到循环内的下一个数
for(int i = 0;i < mod; ++i) nx[i] = Move(i), inc[i] = true;
    for(int i = 0;i < mod; ++i) if(!vis[i]) {
        int y; for(y = i; !vis[y]; y = nx[y]) vis[y] = true;
        for(int t = i; t != y; t = nx[t]) inc[t] = false;
    }
Step2:初始化与区间维护

对于一个数,如果其进入循环节,我们就处理出其所有的循环存在C[i]中,然后标记这个数进入循环,并将pos值零。
对于一个区间,如果其进入循环节,当且仅当其子区间都进入循环节。并且其循环节长度为其子区间循环节长度的lcm

//Merge表示按照题目要求把子区间的答案合并到当前区间。
void Init(int p, int v) {
    val[p] = v; in[p] = inc[v];
    if(in[p]) {
        len[p] = 1; C[p][pos[p] = 0] = v;
        for(int i = nx[v]; i != v; i = nx[i]) C[p][len[p]++] = i;
    }
}
void Update(int p) {
    int lc = p << 1, rc = lc | 1;
    val[p] = Merge(val[lc], val[rc]); in[p] = in[lc] & in[rc];
    if(in[p]) {
        len[p] = lcm(len[lc], len[rc]);
        int L = pos[lc], R = pos[rc];
        for(int i = 0;i < len[p]; ++i) {
            C[p][i] = Merge(C[lc][L++], C[rc][R++]);
            if(L == len[lc]) L = 0; if(R == len[rc]) R = 0;
        }
        pos[p] = 0;
    }
}
Step3:区间修改与懒人标记

这里的区间修改就是指循环节的移动。懒人标记里面储存的就是懒人标记移动的距离。用线段树正常维护即可。注意对于没有完全进入循环节的区间我们必须暴力往下操作。直到整个区间完全进入循环的时候才可以懒人标记。

void Tag(int p, int v) {
    tag[p] += v; pos[p] += v;
    if(pos[p] >= len[p]) pos[p] %= len[p]; val[p] = C[p][pos[p]];
}
void Pushdown(int p) {
    if(tag[p]) {
        Tag(p << 1, tag[p]);
        Tag(p << 1 | 1, tag[p]);
        tag[p] = 0;
    }
}
void Change(int p, int L, int R, int st, int ed) {
    if(L == st && ed == R && in[p]) {Tag(p, 1); return;}
    if(L == R) {Init(p, nx[val[p]]); return;}
    int mid = L + R >> 1; Pushdown(p);
    if(st <= mid) Change(p << 1, L, mid, st, min(mid, ed));
    if(ed > mid) Change(p << 1 | 1, mid + 1, R, max(st, mid + 1), ed);
    Update(p);
}

区间查询和普通线段树没有区别。

时间复杂度

时间复杂度为 O(mnlogn) O ( m n l o g n ) 这里的m指的是循环节长度。

代码

拥有上述知识点,这道题就是裸题啦!

/**************************************************************
    Problem: 4105
    User: 2014lvzelong
    Language: C++
    Result: Accepted
    Time:12708 ms
    Memory:105260 kb
****************************************************************/

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 1e5 + 10, T = 4e5 + 10, P = 1e4 + 10;
int a[N], val[T], C[T][62], nx[P], len[T], pos[T], tag[T], mod;
bool inc[P], in[T], vis[P];
int read() {
    char ch = getchar(); int x = 0;
    while(ch < '0' || ch > '9') ch = getchar();
    for(;ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) - '0' + ch;
    return x;
}
int lcm(int a, int b) {return a / __gcd(a, b) * b;}
void Init(int p, int v) {
    val[p] = v; in[p] = inc[v];
    if(in[p]) {
        len[p] = 1; C[p][pos[p] = 0] = v;
        for(int i = nx[v]; i != v; i = nx[i]) C[p][len[p]++] = i;
    }
}
void Tag(int p, int v) {
    tag[p] += v; pos[p] += v;
    if(pos[p] >= len[p]) pos[p] %= len[p]; val[p] = C[p][pos[p]];
}
void Pushdown(int p) {
    if(tag[p]) {
        Tag(p << 1, tag[p]);
        Tag(p << 1 | 1, tag[p]);
        tag[p] = 0;
    }
}
void Update(int p) {
    int lc = p << 1, rc = lc | 1;
    val[p] = val[lc] + val[rc]; in[p] = in[lc] & in[rc];
    if(in[p]) {
        len[p] = lcm(len[lc], len[rc]);
        int L = pos[lc], R = pos[rc];
        for(int i = 0;i < len[p]; ++i) {
            C[p][i] = C[lc][L++] + C[rc][R++];
            if(L == len[lc]) L = 0; if(R == len[rc]) R = 0;
        }
        pos[p] = 0;
    }
}
void Build(int p, int L, int R) {
    if(L == R) {Init(p, a[L]); return ;}
    int mid = L + R >> 1;
    Build(p << 1, L, mid);
    Build(p << 1 | 1, mid + 1, R);
    Update(p);
}
void Change(int p, int L, int R, int st, int ed) {
    if(L == st && ed == R && in[p]) {Tag(p, 1); return;}
    if(L == R) {Init(p, nx[val[p]]); return;}
    int mid = L + R >> 1; Pushdown(p);
    if(st <= mid) Change(p << 1, L, mid, st, min(mid, ed));
    if(ed > mid) Change(p << 1 | 1, mid + 1, R, max(st, mid + 1), ed);
    Update(p);
}
int Query(int p, int L, int R, int st, int ed) {
    if(L == st && ed == R) return val[p];
    int mid = L + R >> 1; Pushdown(p); int ans = 0;
    if(st <= mid) ans += Query(p << 1, L, mid, st, min(mid, ed));
    if(ed > mid) ans += Query(p << 1 | 1, mid + 1, R, max(st, mid + 1), ed);
    return ans;
}

int main() {
    int n = read(), m = read(); mod = read();
    for(int i = 1; i <= n; ++i) a[i] = read();
    for(int i = 0;i < mod; ++i) nx[i] = (i * i) % mod, inc[i] = true;
    for(int i = 0;i < mod; ++i) if(!vis[i]) {
        int y; for(y = i; !vis[y]; y = nx[y]) vis[y] = true;
        for(int t = i; t != y; t = nx[t]) inc[t] = false;
    }
    Build(1, 1, n);
    while(m--) {
        int opt = read(), L = read(), R = read();
        if(!opt) Change(1, 1, n, L, R);
        else printf("%d\n", Query(1, 1, n, L, R));
    }
    return 0; 
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值