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(1≤n≤106) 个球,比赛结果可以用长度为
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) 表示结果,其具体定义如下:
- 检查在当前 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,∣cntW−cntL∣≥2 ,其中 c n t W cnt_W cntW 表示前缀中"W"的数量, c n t L cnt_L cntL 表示前缀中"L"的数量。
- 如果不存在这样的前缀,则结束。
- 如果存在,选择最短的满足条件的前缀,若 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=1∣S∣fi(S)×(∣S∣+1)i−1mod998244353 。
题解
根据题意,若不考虑赢球差至少为 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| 1≤i≤∣S∣ )表示打了第 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;
}