A Simple Problem with Integers 循环节 修改 平方 找规律 线段树

A Simple Problem with Integers

这个题目首先要打表找规律,这个对2018取模最后都会进入一个循环节,这个循环节的打表要用到龟兔赛跑。

龟兔赛跑算法 floyed判环算法

这个算法我觉得还是很有意思的,可以学习一下。

不过这个题目这个算法打表还是有点难写的。

由这个算法可以得到这个循环节的最大长度是6 最大入环距离是4.

为什么有些循环节不是6,还是可以用6呢,因为每个元素平方最大周期为6,且6是其他所有周期的公倍数。

然后学完之后还是不太会这个题目怎么写,2018 ACM-ICPC 上海大都会 H A Simple Problem with Integers(level 4)(线段树+floyd判环+暴力)

研究了一下这个人的代码,才稍微会了一点点。

这个题目首先是要判断有没有进入负环,如果一个区间的所有叶子节点都已经进入循环节,tim数组

那么这个以后就可以用延迟标记直接记录这个已经推到了这个数的循环节的第几个,mark数组

推到了循环节的第几个可以用pos数组记录,pos数组

然后就是建树,用一个二维的数组来记录,第二维是pos,代表这个数要返回的循环节的位置。

知道这些就可以自己敲代码了。

敲代码的时候越发感觉这个代码写的巧妙之处了,首先它预处理了0~2017 这样之后就可以不用求一个数的平方了,直接迭代。

然后就是这个pos数组,如果来不及push_up 就要求输出结果,这个就已经记录了,然后可以通过这个pos数组来push_up

然后就是这个树的第二维,第二维记录了从这个往后推的6个数,而且每次push_up一次就会把第二维的第一个位置更新为我们所要的结果。

这个题目很好,代码也很巧妙,值得学习。

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <queue>
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
#include <map>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 5e4 + 10;
const int mod = 2018;
ll mp[maxn], a[maxn];
ll tree[maxn * 4][10];
int tim[maxn * 4], pos[maxn * 4], lazy[maxn * 4];

void push_up(int id)
{
    for (int i = 0; i < 6; i++)//这个for循环保证i==0的位置就是答案,并且推出了后面的五个位置的数
        //这个的主要目的是因为只有在叶子节点的pos才会有值,只要不是叶子节点就都初始化为0,所以我们要保证tree[id][0]的位置就是答案
        tree[id][i] = tree[id << 1][(pos[id << 1] + i) % 6] + tree[id << 1 | 1][(pos[id<<1|1] + i) % 6];
    tim[id] = min(tim[id << 1], tim[id << 1 | 1]);
    pos[id] = 0;//为什么这个地方可以赋值为0呢,因为上面的那层for循环已经保证了i==0 的位置就是答案
    // printf("tree[%d][0]=%lld
", id, tree[id][0]);
}

void build(int id,int l,int r)
{
    tim[id] = pos[id] = lazy[id] = 0;
    if(l==r)
    {
        tree[id][0] = a[l];//一开始a[l]就是查询结果
        for (int i = 1; i < 6; i++) tree[id][i] = mp[tree[id][i - 1]];//这个就是在找a[l]的平方,a[l]平方的平方...
        return;
    }
    int mid = (l + r) >> 1;
    build(id << 1, l, mid);
    build(id << 1 | 1, mid + 1, r);
    push_up(id);
}

void push_down(int id)
{
    pos[id << 1] = (pos[id << 1] + lazy[id]) % 6;
    pos[id << 1 | 1] = (pos[id << 1 | 1] + lazy[id]) % 6;
    lazy[id << 1] += lazy[id];
    lazy[id << 1 | 1] += lazy[id];
    lazy[id] = 0;
}

void update(int id,int l,int r,int x,int y)
{
    if (y<l || x>r) return;
    if(x<=l&&y>=r&&tim[id]>4)
    {
        lazy[id]++;
        pos[id] = (pos[id] + 1) % 6;//为什么这个地方不求出结果,因为求不出来,这个只可以往下传递lazy 标志,并且记录这个是在循环节的哪一个位置
        //然后这个位置的上一个节点被更新,或者说这个pos记录就是一种表示求结果的方式,因为之后输出的就是tree[id][pos[id]]
        return;
    }
    if(l==r)
    {
        pos[id] = 0;//如果还是在暴力的阶段就必须赋值为0,pos 和 lazy 只有在进入循环节之后才会有
        lazy[id] = 0;
        tim[id]++;
        tree[id][0] = mp[tree[id][0]];
        for (int i = 1; i < 6; i++) tree[id][i] = mp[tree[id][i - 1]];
        return;
    }
    push_down(id);
    int mid = (l + r) >> 1;
    if (x <= mid) update(id << 1, l, mid, x, y);
    if (y > mid) update(id << 1 | 1, mid + 1, r, x, y);
    push_up(id);
}

ll query(int id,int l,int r,int x,int y)
{
    if (x > r || y < l) return 0;
    // printf("id=%d l=%d r=%d x=%d y=%d
", id, l, r, x, y);
    if (x <= l && y >= r) return tree[id][pos[id]];
    int mid = (l + r) >> 1;
    push_down(id);
    ll ans = 0;
    if (x <= mid) ans = query(id << 1, l, mid, x, y);
    if (y > mid) ans += query(id << 1 | 1, mid + 1, r, x, y);
    return ans;
}

int main()
{
    for (int i = 0; i < 2018; i++) mp[i] = i * i%mod;
    int t;
    scanf("%d", &t);
    for(int cas=1;cas<=t;cas++)
    {
        int n, m;
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
        build(1, 1, n);
        scanf("%d", &m);
        printf("Case #%d:
",cas);
        while (m--) {
            char s[10];
            int l, r;
            scanf("%s%d%d", s, &l, &r);
            if (l > r) swap(l, r);
            if (s[0] == 'Q') {
                ll ans = query(1, 1, n, l, r);
                printf("%lld
", ans);
            }
            else update(1, 1, n, l, r);
        }
    }
    return 0;
}
原文地址:https://www.cnblogs.com/EchoZQN/p/11322273.html