2018上海大都会 H题, 线段树(平方取模操作)

链接:https://www.nowcoder.com/acm/contest/163/H
来源:牛客网
 

题目描述

You have N integers A1, A2, ... , AN. You are asked to write a program to receive and execute two kinds of instructions:

1. C a b means performing Ai = (Ai2 mod 2018) for all Ai such that a ≤ i ≤ b.
2. Q a b means query the sum of Aa, Aa+1, ..., Ab. Note that the sum is not taken modulo 2018.

输入描述:

The first line of the input is T(1≤ T ≤ 20), which stands for the number of test cases you need to solve.
The first line of each test case contains N (1 ≤ N ≤ 50000).The second line contains N numbers, the initial values of A1, A2, ..., An.  0 ≤ Ai < 2018. The third line contains the number of operations Q (0 ≤ Q ≤ 50000). The following Q lines represents an operation having the format "C a b" or "Q a b", which has been described above. 1 ≤ a ≤ b ≤ N.

输出描述:

For each test case, print a line "Case #t:" (without quotes, t means the index of the test case) at the beginning.
You need to answer all Q commands in order. One answer in a line.

 

示例1

输入

复制

1
8
17 239 17 239 50 234 478 43
10
Q 2 6
C 2 7
C 3 4
Q 4 7
C 5 8
Q 6 7
C 1 8
Q 2 5
Q 3 4
Q 1 8

输出

复制

Case #1:
779
2507
952
6749
3486
9937

这个题真的好强啊。一开始啥也不会,后来请教了线段树大神舍友慢慢学会了一点点。

对于平方取模操作,在2018 以内的每一个数,一定可以通过若干步进入循环节(对于这个题好像是最多4步,如果打表没错的话), 并且也可以暴力的求出循环节最长为6 ,那么这样这个题就找到了突破口。我们可以从循环节入手。 在每个节点中记录该节点的循环节,以及该节点的sum在循环节的哪个位置,这样每次操作一次右移一次,对于一个父节点,只有他的两个子节点全部进入循环节,那么这个节点才可以进入循环节。 而且这里有个小操作,大神的方法是可以预先将每个叶节点向前走> 4 步 这样记录的这个叶节点的val 就一定是进入循环节的。 代码中有一点点解释。

代码: 

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
//
#define lson (i<<1)
#define rson (i<<1|1)

using namespace std;
const int N =5e4+5;
int a[N];
int pre[N][15];

struct node
{
    int l,r,sum;
    int pos,lz;
    int cnt,flag;
    int val[7];
}tr[N<<2];

void init()
{
    for(int i=0;i<2018;i++){
        int tmp=i;
        for(int j=0;j<15;j++){
            pre[i][j]=tmp;
            tmp=(tmp*tmp)%2018;
        }
    }
}

void push_up(int i)
{
    tr[i].sum=tr[lson].sum+tr[rson].sum;
    if(tr[lson].flag&&tr[rson].flag){  // 如果两个子节点都进入循环节,那么标志着这个父节点也可以进入循环节
            // 循环节中的数其实是两个子节点的加和。
        tr[i].pos=0;
        for(int j=0;j<6;j++){
            tr[i].val[j]=tr[lson].val[(tr[lson].pos+j)%6]+tr[rson].val[(tr[rson].pos+j)%6];
        }
        tr[i].flag=1;
    }
}

void push_down(int i)
{
    if(tr[i].lz)
    {
        int xx=tr[i].lz;
        tr[lson].lz+=xx; tr[rson].lz+=xx;
        tr[lson].lz%=6; tr[rson].lz%=6;
        tr[lson].pos=(tr[lson].pos+xx)%6; tr[rson].pos=(tr[rson].pos+xx)%6;
        tr[lson].sum=tr[lson].val[tr[lson].pos]; tr[rson].sum=tr[rson].val[tr[rson].pos];
        tr[i].lz=0;
    }
}

void build(int i,int l,int r)
{
    tr[i].l=l; tr[i].r=r; tr[i].sum=0; tr[i].lz=0; tr[i].pos=0; tr[i].flag=0; tr[i].cnt=0;
    for(int j=0;j<6;j++) tr[i].val[j]=0;
    if(l==r){
        tr[i].sum=a[l];
        for(int j=0;j<6;j++){
            tr[i].val[j]=pre[a[l]][5+j];
        }
        return ;
    }
    int mid=(tr[i].l+tr[i].r)>>1;
    build(lson,l,mid);
    build(rson,mid+1,r);
    push_up(i);
}

void update(int i,int l,int r)
{
    if(tr[i].l==l&&tr[i].r==r){
        if(tr[i].flag){
            tr[i].pos=(tr[i].pos+1)%6;
            tr[i].lz=(tr[i].lz+1)%6;
            tr[i].sum=tr[i].val[tr[i].pos];
            return ;
        }
        if(l==r){ // 表示如果是根节点 并且还没有进入循环
            tr[i].cnt++;
            tr[i].sum=(tr[i].sum*tr[i].sum)%2018;
            if(tr[i].cnt==5) tr[i].flag=1;
            return ;
        }
        // 如果当前节点不是根节点并且还没有进入循环节,那么就要向下继续更新.
    }
    push_down(i);
    int mid=(tr[i].l+tr[i].r)>>1;
    if(r<=mid) update(lson,l,r);
    else if(l>mid) update(rson,l,r);
    else{
        update(lson,l,mid); update(rson,mid+1,r);
    }
    push_up(i);
}

int query(int i,int l,int r)
{
    if(tr[i].l==l&&tr[i].r==r){
        return tr[i].sum;
    }
    push_down(i);
    int mid=(tr[i].l+tr[i].r)>>1;
    if(r<=mid) return query(lson,l,r);
    else if(l>mid) return query(rson,l,r);
    else{
        return query(lson,l,mid)+query(rson,mid+1,r);
    }
}

char op[5];
int n,m;

int main()
{
    int l,r;
    init();
    int T;
    scanf("%d",&T);
    int kk=0;
    while(T--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
        }
        build(1,1,n);
        scanf("%d",&m);
        printf("Case #%d:\n",++kk);
        while(m--)
        {
            scanf("%s %d %d",op,&l,&r);
            if(op[0]=='C'){
                update(1,l,r);
            }
            else{
                int ans=query(1,l,r);
                printf("%d\n",ans);
            }
        }
    }
    return 0;
}

另附一份打表的代码:

#include<bits/stdc++.h>

using namespace std;

int ans[10005];
int cc[10005];
int vis[2020];

int main()
{
    int tot=0;
    int mincnt=1000005;
    for(int num=0;num<2018;num++)
    {
        int cnt=0;
        int tmp=num;
        int f=0;
        while(1)
        {
            cnt++;
            tmp=tmp*tmp%2018;
            //cout<<"cnt "<<cnt<<" tmp "<<tmp<<endl;
            if(tmp==num) break;
            if(cnt>105){
                f=1;
                break;
            }
        }
        if(f==0){
            ans[++tot]=num;
            cc[tot]=cnt;
        }
    }

    cout<<"mincnt "<<mincnt<<endl;
    for(int i=1;i<=tot;i++){
        cout<<" num "<<ans[i]<<" cc "<<cc[i]<<endl;
    }
    for(int i=1;i<=tot;i++){
        vis[ans[i] ]=1;
    }

    int maxx=-1;
    for(int num=0;num<2018;num++)
    {
        int cnt=0;
        int tmp=num;
        int f=0;
        while(1)
        {
            if(vis[tmp]) break;
            cnt++;
            tmp=tmp*tmp%2018;
        }
        maxx=max(maxx,cnt);
    }
    cout<<"maxx "<<maxx<<endl;


    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值