题目
问题描述
输入第一行输入两个整数
n
,
q
(
1
≤
n
,
q
≤
2
×
1
0
5
)
n,q(1\leq n,q \leq 2\times 10^5)
n,q(1≤n,q≤2×105)表示游戏结果字符串长度与询问次数。
第二行输入一个字符串,表示游戏结果字符串,保证之中只含有
W
、
L
、
D
W、L、D
W、L、D三种字符,分别表示胜利、失败、平局。
需要注意的是:若你当前的分数是
3
3
3的整倍数(包括
0
0
0倍),则若下一局游戏失败,你的分数将不变(而不是减一)。
接下来
q
q
q行,每行三个数
l
,
r
,
s
(
1
≤
l
,
r
≤
n
,
0
≤
s
≤
1
0
9
)
l,r,s(1\leq l,r \leq n, 0\leq s \leq 10^9)
l,r,s(1≤l,r≤n,0≤s≤109)代表一组询问,每次询问格式为
(
l
,
r
,
s
)
(l,r,s)
(l,r,s),询问若你初始有
s
s
s分,按从左到右的顺序经历了
[
l
,
r
]
[l,r]
[l,r]这一子串的游戏结果后,最终分数是多少。
ST表解法
通过题目所给样例不难发现,对于同一个询问区间,不同的 s s s的结果只有 3 3 3种情况,而且是依据其模 3 3 3的值而不同。
S T 表 ST表 ST表可以用于解决 R M Q RMQ RMQ问题,如果我们可以得知 S T [ k ] [ i ] [ j ] ST[k][i][j] ST[k][i][j]的值,也就是在 s % 3 = = k s\%3==k s%3==k时,经过区间 [ i , i + 2 j − 1 ] [i,i+2^j-1] [i,i+2j−1]带来的分数变化,就可以通过不断枚举区段的方法求出目标区间的对 s s s的分数影响。
初始边界
S
T
[
k
]
[
i
]
[
0
]
ST[k][i][0]
ST[k][i][0]可以直接确定,也就是只有一个结果的值,如果想要求出
S
T
[
k
]
[
i
]
[
j
]
ST[k][i][j]
ST[k][i][j],按照原来在
S
T
表
ST表
ST表中的学习,可以由子区间
[
i
,
i
+
2
j
−
1
−
1
]
与
[
i
+
2
j
−
1
,
i
+
2
j
−
1
]
[i,i+2^{j-1}-1]与[i+2^{j-1},i+2^{j}-1]
[i,i+2j−1−1]与[i+2j−1,i+2j−1]的和求出。
但要注意的是,处理第一个区间是在情况
k
k
k,而处理第二个区间的时候,已经不是情况
k
k
k了,因为分数会发生变化,而应该是情况
(
k
+
s
t
[
k
]
[
i
]
[
j
−
1
]
)
%
3
(k+st[k][i][j-1])\%3
(k+st[k][i][j−1])%3。
处理方案也就是:
st[k][i][j]=st[k][i][j-1]+st[(k+st[k][i][j-1])%3][i+(1<<(j-1))][j-1];
由于这道题不同于一般的可重复贡献问题,我们需要对目标区间进行区段的枚举,枚举就会导致区间的越界,所以我们要对越界的情况进行处理,统一把越界的情况归为临界的情况:
if(i+(1<<(j-1))>n)st[k][i][j]=st[k][i][j-1];
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
const int logn=log2(N);
int n,m,st[3][200005][21],Logn[N];
char a[N];
int main(){
Logn[0]=-1;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
Logn[i]=Logn[i/2]+1;//计算以2为底的对数
if(a[i]=='W'){
st[0][i][0]=1;
st[1][i][0]=1;
st[2][i][0]=1;
}
else if(a[i]=='L'){
st[1][i][0]=-1;
st[2][i][0]=-1;
}
}
for(int j=1;j<=Logn[n];j++){
for(int i=1;i<=n;i++){
for(int k=0;k<=2;k++){
if(i+(1<<(j-1))<=n)st[k][i][j]=st[k][i][j-1]+st[(k+st[k][i][j-1])%3][i+(1<<(j-1))][j-1];
else st[k][i][j]=st[k][i][j-1];
}
}
}
int l,r,s,tmp;
while(m--){
cin>>l>>r>>s;
tmp=l;
while(tmp<=r){
int j=0;
while(tmp+(1<<j)-1<=r)j++;
j--;
s+=st[s%3][tmp][j];
tmp+=(1<<j);
}
cout<<s<<endl;
}
}
线段树解法
既然 S T 表 ST表 ST表可以解决这个问题,那么线段树同样可以解决这个问题。
有两点需要注意,首先,区间信息上传时考虑子区间的情况变化,与 S T 表 ST表 ST表一致。
void pushup(int rt){
for(int i=0;i<3;i++){
tree[rt].val[i]=tree[rt*2].val[i]+tree[rt*2+1].val[(i+tree[rt*2].val[i])%3];
}
}
其次,在访问时同样要考虑子区间的变化
int tmp=query(L,R,rt*2,l,mid,k);
return tmp+query(L,R,rt*2+1,mid+1,r,(tmp+k)%3);
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
struct node{
int val[3];
};
node tree[4*N];
int n,m;
char a[N];
void pushup(int rt){
for(int i=0;i<3;i++){
tree[rt].val[i]=tree[rt*2].val[i]+tree[rt*2+1].val[(i+tree[rt*2].val[i])%3];
}
}
void build(int rt,int l,int r){
if(l==r){
if(a[l]=='W'){
tree[rt].val[0]=tree[rt].val[1]=tree[rt].val[2]=1;
}
else if(a[l]=='L'){
tree[rt].val[0]=0;tree[rt].val[1]=-1;tree[rt].val[2]=-1;
}
else if(a[l]=='D'){
tree[rt].val[0]=tree[rt].val[1]=tree[rt].val[2]=0;
}
return;
}
int mid=(l+r)/2;
build(rt*2,l,mid);
build(rt*2+1,mid+1,r);
pushup(rt);
}
int query(int L,int R,int rt,int l,int r,int k){
if(L<=l&&r<=R){
return tree[rt].val[k];
}
if(l>R||r<L)return 0;
int mid = (l+r)/2;
int tmp=query(L,R,rt*2,l,mid,k);
return tmp+query(L,R,rt*2+1,mid+1,r,(tmp+k)%3);
}
int main(){
cin>>n>>m>>a+1;
build(1,1,n);
int l,r,s;
while(m--){
cin>>l>>r>>s;
cout<<query(l,r,1,1,n,s%3)+s<<endl;
}
}