2018 ACM-ICPC 上海大都會 H A Simple Problem with Integers(level 4)(線段樹+floyd判環+暴力)

題目鏈接

題意:

給你長度爲n的數組,然後有q個操作,2種類型(n,q<=5e4)

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.

解析:

摘了討論的一個題解

因爲模數比較小,考慮自冪多次後會產生循環節。可以使用KMP算法和floyd判環(龜兔賽跑算法)輔助尋找循環節。

KMP可以求出循環長度。

floyd判環則更加強大,不但能求出循環圈的大小,並且能夠區分循環部分與非循環的部分

本題使用floyd判環的模板帖進去會得到如下結論:

1、所有數字均會在多次自冪之後進入一個長度爲6的循環節。

2、一開始有些數字一開始就在循環圈上,有些數字不在循環圈上。但是操作多次之後所有數字一定都會進入循環節,並且這個操作次數不多。

題解:

預處理將0到2017的數字分成兩類,一類是在長度爲6的循環節中的數字,另一類是不在循環節中的數字。

建一顆線段樹,一開始暴力更新。一旦發現區間內所有的數字均進入長度爲6的循環節時,就改爲處理這6種循環的區間和。並且將修改操作從暴力修改改爲懶標記下放。

時間複雜度O(6mlogn),空間複雜度O(n)

 龜兔賽跑算法   代碼

kmp求循環節

下面是我打表的代碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<set>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
#include<list>
#include <queue>
#include <map>
#include <string>
#include <fstream>
using namespace std;
typedef pair<int,int> PII;
typedef long long ll;
const int N = 2018;
int mp[N];

PII cal(int s)
{
    int fast=s;
    int slow=s;
    bool isCircular = true;

    do
    {
        if(mp[fast]==-1||mp[mp[fast]]==-1)
        {
            isCircular =false;
            break;
        }

        fast=mp[mp[fast]];
        slow=mp[slow];
    }while(fast!=slow);
    //確定起點
    slow=s;
    while(slow!=fast)
    {
        slow=mp[slow];
        fast=mp[fast];
    }

    //環的長度
    int i=0;
    do
    {
        slow=mp[slow];
        i++;
    }
    while(slow!=fast);
    //printf("環的起點:%d\n環的長度:%d\n",slow,i);
    return make_pair(slow,i);
}

int main()
{
    for(int i=0;i<2018;i++)
    {
        mp[i]=i*i%2018;
    }
    int maxstep=0;
    int maxcir=0;
    for(int i=0;i<2018;i++)
    {
        PII tmp=cal(i);
        maxcir=max(tmp.second,maxcir);
        int j=0;
        int x=i;
        while(x!=tmp.first)
        {
            x=mp[x];
            j++;
        }
        maxstep=max(maxstep,j);
    }
    printf("最大環:%d\n最大入環距離:%d\n",maxcir,maxstep);
}

這道題關鍵的解題點是你最多走4步之後會進入一個環。

如果線段樹某一個點表示的[l,r]內的點都進入循環,那麼我們只需要爲線段樹的節點開一個大小爲6的數組sum[][6],

分別記錄這個區間內的數都走i步後的和。

那麼如果更新的區間只是[nl,r]  (nl>l),怎麼辦?

其實這個問題可以用線段樹本身的性質解決。如果你更新了[nl,r],那麼我們就會向下更新,直到找到[s1,e1]

使得s1>=nl&&e1<=r。那麼對於這樣的區間,我們就更新我們上面說過的那個數組sum[][6]

(如果已經進入環了,那麼只需要移動指針;否則,就需要整個重新求一編)

那麼更新好這個點,我們只需要向上合併的時候,更新上面的sum[root][i]=sum[lch][i]+sum[rch][i] (i=0,...6)

 

 

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <bitset>
#define lch (root<<1)
#define rch ((root<<1)|1)
using namespace std;
const int N = 5e4+10;
const int CIR = 6;
 
int Btree[N*4][6],tim[N*4],pos[N*4];
int mark[N*4];
int stu[N];
int mp[N];
 
void push_up(int root)
{
    for(int i=0;i<CIR;i++)
        Btree[root][i]=Btree[lch][(pos[lch]+i)%CIR]+Btree[rch][(pos[rch]+i)%CIR];
    tim[root]=min(tim[lch],tim[rch]);
    pos[root]=0;
}
 
void pushDown(int root)
{
    pos[rch]=(pos[rch]+mark[root])%CIR;
    pos[lch]=(pos[lch]+mark[root])%CIR;
    mark[lch]+=mark[root];
    mark[rch]+=mark[root];
    mark[root]=0;
 
}
 
void update(int root,int l,int r,int s2,int e2)   //s1,e1表示當前區間,s2,e2表示目標區間
{
     
    if(l>=s2&&r<=e2&&tim[root]>4)
    {
        pos[root]=(pos[root]+1)%CIR;
        mark[root]++;
        return;
    }
    else if(l==r)
    {
        Btree[root][0]=mp[Btree[root][0]];
        for(int i=1;i<CIR;i++)
            Btree[root][i]=mp[Btree[root][i-1]];
        pos[root]=0;
        tim[root]++;
        mark[root]=0;
        return;
    }
    pushDown(root);
    int mid=(l+r)/2;
    if(s2<=mid)
        update(root*2,l,mid,s2,e2);
    if(mid+1<=e2)
        update(root*2+1,mid+1,r,s2,e2);
     
    push_up(root);
 
}
 
 
void build(int l,int r,int root)  //l,r表示他們在stu中的下標,root表示他們在線段樹中的座標
{
    if(l>r)return;
    if(l==r)
    {
        Btree[root][0]=stu[l];
        for(int i=1;i<CIR;i++)
            Btree[root][i]=mp[Btree[root][i-1]];
        pos[root]=0;
        tim[root]=0;
        mark[root]=0;
        return;
    }
    mark[root]=0;
    int mid=(l+r)/2;
     
    build(l,mid,root*2);
    build(mid+1,r,root*2+1);
 
    push_up(root);
}
 
int query(int root,int s1,int e1,int s2,int e2)
{
     
    if(s1>=s2&&e1<=e2)
    {
        return Btree[root][pos[root]];
    }
  
    pushDown(root);
    int mid=(s1+e1)>>1;
    int ans=0;
    if(s2<=mid) ans+=query(root<<1,s1,mid,s2,e2);
    if(mid+1<=e2) ans+=query((root<<1)|1,mid+1,e1,s2,e2);
    return ans;
}
 
 
 
 
int main()
{
     
    for(int i=0;i<2018;i++)
    {
        mp[i]=i*i%2018;
    }
    int t;
    scanf("%d",&t);
    int cas=0;
    while(t--)
    {
        cas++;
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&stu[i]);
        }
        build(1,n,1);
        int q;
        scanf("%d",&q);
        printf("Case #%d:\n",cas);
        for(int i=0;i<q;i++)
        {
            char c;
            int l,r;
            getchar();
            scanf("%c%d%d",&c,&l,&r);
            if(c=='Q')
            {
                printf("%d\n",query(1,1,n,l,r));
            }
            else
            {
                update(1,1,n,l,r);
            }
        }
    }
     
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章