2021牛客暑期多校训练营5 C题: Cheating and Stealing

C题: Cheating and Stealing

原题链接:https://ac.nowcoder.com/acm/contest/11256/C

题目大意

G和P两个人打乒乓,共打了 n ( 1 ≤ n ≤ 1 0 6 ) n(1\le n\le 10^6) n(1n106) 个球,比赛结果可以用长度为 n n n 的由"W"和"L"组成的字符串 S S S 表示,"W"表示G赢了一球,"L"表示P赢了一球。
求对于任意 i ∈ { 1 , 2 , . . . , n } i\in \{1,2,...,n\} i{1,2,...,n} ,如果一局比赛的满分为 i i i ,G可以赢多少局。用 f i ( S ) f_i(S) fi(S) 表示结果,其具体定义如下:

  1. 检查在当前 S S S 中,是否存在任意前缀的 max ⁡ ( c n t W , c n t L ) ≥ i , ∣ c n t W − c n t L ∣ ≥ 2 \max(cnt_W,cnt_L)\ge i,\left|cnt_W-cnt_L\right|\ge 2 max(cntW,cntL)i,cntWcntL2 ,其中 c n t W cnt_W cntW 表示前缀中"W"的数量, c n t L cnt_L cntL 表示前缀中"L"的数量。
  2. 如果不存在这样的前缀,则结束。
  3. 如果存在,选择最短的满足条件的前缀,若 c n t W > c n t L cnt_W>cnt_L cntW>cntL ,则 f i ( S ) f_i(S) fi(S) 1 1 1 ,反之不变。然后删除这段前缀,回到第一步。

由于结果很大,输出 ∑ i = 1 ∣ S ∣ f i ( S ) × ( ∣ S ∣ + 1 ) i − 1   m o d   998244353 \sum^{\left|S\right|}_{i=1}f_i(S)\times(\left|S\right|+1)^{i-1}\bmod 998244353 i=1Sfi(S)×(S+1)i1mod998244353

题解

根据题意,若不考虑赢球差至少为 2 2 2 的条件,易得局数为 n ( i = 1 ) + n 2 ( i = 2 ) + n 3 ( i = 3 ) + . . . n(i=1)+\frac{n}{2}(i=2)+\frac{n}{3}(i=3)+... n(i=1)+2n(i=2)+3n(i=3)+... ,呈现出调和级数的形式,则总局数至多为 n  ⁣ l o g n n\!_{log}n nlogn ,若我们能实现以近 O ( 1 ) O(1) O(1) 的复杂度查询每一局的结果,则复杂度显然是可行的。

为了方便查询赢球状态,我们用前缀和 s u m W i sumW_i sumWi s u m L i sumL_i sumLi ( 1 ≤ i ≤ ∣ S ∣ 1\le i\le \left|S\right| 1iS )表示打了第 i i i 球后,双方的赢球数(即 S S S 的前 i i i 个字符中"W"和"L"的个数), p o s W i posW_i posWi p o s L i posL_i posLi 分别表示第 i i i 个"W"和第 i i i 个"L"的位置。若上一局的结束位置为 x x x ,则下一局的结束位置应为 min ⁡ ( p o s W s u m W x + i , p o s L s u m L x + i ) \min(posW_{sumW_x+i},posL_{sumL_x+i}) min(posWsumWx+i,posLsumLx+i) (在不考虑赢球差的情况下)

对于赢球差需要至少为 2 2 2 才结束的条件,我们不难发现若赢球差小于 2 2 2 时,两者总是呈现轮流赢球的状态(可能会连续 2 2 2 球,但此种情况下查询较快),我们设 B i B_i Bi 表示 i i i 位置后第一次出现的连续 2 2 2 个"W"或"L"的位置,则每次查询下一局结束位置时我们判断赢球差是否满足条件,若不满足则持续用 B B B 数组递推更新下一局结束位置。

参考代码

#include<bits/stdc++.h>
#define For(i,n,m) for(int i=n;i<=m;i++)
#define FOR(i,n,m) for(int i=n;i>=m;i--)
#define mod 998244353
using namespace std;

const int MAXN=1e6+5;
int n,sumW[MAXN],sumL[MAXN],posW[MAXN],posL[MAXN],B[MAXN];
char S[MAXN];

int powmod(int x,int p){int ret=1;while(p){if(p&1)ret=1ll*ret*x%mod;x=1ll*x*x%mod;p>>=1;}return ret;}
//快速幂
int main()
{
	std::ios::sync_with_stdio(false),cin.tie(0);
	cin>>n;
	cin>>S+1;//读入下标从1开始
	int u=0,v=0,last=n+1;//last初值赋为n+1,表示其后没有连续"W"或"L"
	For(i,1,n){
		sumW[i]=sumW[i-1]+(S[i]=='W');//"W"前缀和
		sumL[i]=sumL[i-1]+(S[i]=='L');//"L"前缀和
		if(S[i]=='W')posW[++u]=i;//更新"W"位置表
		else posL[++v]=i;//更新"L"位置表
	}
	B[n]=n+1;
	FOR(i,n-1,1){
		if(S[i]==S[i+1])last=i+1;//若有连续出现则更新last
		B[i]=last;//初始化B数组
	}
	int ans=0;
	For(k,1,n){
		int s=0,ss,sum=0;
		while(sumW[s]+k<=u||sumL[s]+k<=v){//若剩下的"W"和"L"都不足则可以直接弹出
			if(s>n)break;
			if(sumW[s]+k>u){//若剩下的"W"不足则只需尝试查询"L"
				ss=posL[sumL[s]+k];//预计的最早位置
				if((sumL[ss]-sumL[s])-(sumW[ss]-sumW[s])<2){
					while(abs((sumL[ss]-sumL[s])-(sumW[ss]-sumW[s]))<2){//若赢球差一直不满足则持续递推更新
						if(ss>n)break;//若非法则弹出
						ss=B[ss];
					}
					if(ss>n)break;//若非法则弹出
					s=ss;//更新到目标位置
					if(S[s]=='W')sum++;//查询该局胜者(即最后一发赢球是谁的)并更新sum
				}
				else s=ss;//查询的是"L",若预计位置满足赢球差,则只需更新到目标位置
			}
			else if(sumL[s]+k>v){//若剩下的"L"不足则只需尝试查询"W"
				ss=posW[sumW[s]+k];//预计的最早位置
				if((sumW[ss]-sumW[s])-(sumL[ss]-sumL[s])<2){
					while(abs((sumW[ss]-sumW[s])-(sumL[ss]-sumL[s]))<2){//若赢球差一直不满足则持续递推更新
						if(ss>n)break;//若非法则弹出
						ss=B[ss];
					}
					if(ss>n)break;//若非法则弹出
					s=ss;//更新到目标位置
					if(S[s]=='W')sum++;//查询该局胜者(即最后一发赢球是谁的)并更新
				}
				else s=ss,sum++;//查询的是"W",若预计位置满足赢球差,则同时更新位置和sum
			}
			else if(posW[sumW[s]+k]<posL[sumL[s]+k]){//剩余的"W"和"L"都足够,则比较位置前后
				ss=posW[sumW[s]+k];//"W"较早出现,查询"W"
				if((sumW[ss]-sumW[s])-(sumL[ss]-sumL[s])<2){
					while(abs((sumW[ss]-sumW[s])-(sumL[ss]-sumL[s]))<2){//若赢球差一直不满足则持续递推更新
						if(ss>n)break;//若非法则弹出
						ss=B[ss];
					}
					if(ss>n)break;//若非法则弹出
					s=ss;//更新到目标位置
					if(S[s]=='W')sum++;//查询该局胜者(即最后一发赢球是谁的)并更新
				}
				else s=ss,sum++;//查询的是"W",若预计位置满足赢球差,则同时更新位置和sum
			}
			else{
				ss=posL[sumL[s]+k];//"L"较早出现,查询"L"
				if((sumL[ss]-sumL[s])-(sumW[ss]-sumW[s])<2){
					while(abs((sumL[ss]-sumL[s])-(sumW[ss]-sumW[s]))<2){//若赢球差一直不满足则持续递推更新
						if(ss>n)break;//若非法则弹出
						ss=B[ss];
					}
					if(ss>n)break;//若非法则弹出
					s=ss;//更新到目标位置
					if(S[s]=='W')sum++;//查询该局胜者(即最后一发赢球是谁的)并更新
				}
				else s=ss;//查询的是"L",若预计位置满足赢球差,则只需更新到目标位置
			}
		}
		ans=(ans+1ll*sum*powmod(n+1,k-1)%mod)%mod;//将sum更新加入ans中,注意快速幂和取模
	}
	cout<<ans<<endl;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值