炸鸡块君与FIFA22(线段树,ST表)

题目

原题链接


问题描述

输入第一行输入两个整数 n , q ( 1 ≤ n , q ≤ 2 × 1 0 5 ) n,q(1\leq n,q \leq 2\times 10^5) n,q(1n,q2×105)表示游戏结果字符串长度与询问次数。
第二行输入一个字符串,表示游戏结果字符串,保证之中只含有 W 、 L 、 D W、L、D WLD三种字符,分别表示胜利、失败、平局。
需要注意的是:若你当前的分数是 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(1l,rn,0s109)代表一组询问,每次询问格式为 ( 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+2j1]带来的分数变化,就可以通过不断枚举区段的方法求出目标区间的对 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+2j11][i+2j1,i+2j1]的和求出。
但要注意的是,处理第一个区间是在情况 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][j1])%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;
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值