题意:
给你长度为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)
下面是我打表的代码
#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);
}
}
}
}