欢迎来我的博客看这篇题解!
Problem
在两人竞技比赛中,对于任何正整数 a a a ,我们定义 B O ( 2 a − 1 ) BO(2 a-1) BO(2a−1) 如下:两名玩家继续竞争,直到其中一人获胜 a a a 次,那么他赢得整个比赛。 B O ( 2 a − 1 ) BO(2 a-1) BO(2a−1) 最多包含 2 a − 1 2a-1 2a−1 小局游戏,最少包含 a a a 小局游戏。
现在两个人进行一场 DotA2 比赛,使用的是 B O ( 2 b − 1 ) of B O ( 2 a − 1 ) BO(2b-1)\ \texttt{of}\ BO(2a-1) BO(2b−1) of BO(2a−1) 赛制。该赛制由最多 2 b − 1 2b-1 2b−1 最少 b b b 场主要比赛组成,每个主要比赛都是一个 B O ( 2 a − 1 ) BO(2a-1) BO(2a−1),由最多 2 a − 1 2a-1 2a−1 最少 a a a 局次要比赛组成。
假如比赛的结果是预先确定的:有一个长度为
n
n
n 的
0
−
1
0-1
0−1 串
T
T
T ,其中 1
表示 A 获胜,0
表示 B 获胜。A 和 B 的每一局次要游戏结果都从串
T
T
T 中获取。假如从
T
T
T 串的每个位置开始重复获取次要游戏结果,求最后谁赢了?
1 ≤ n , a , b ≤ 1 0 5 1\le n,a,b\le 10^5 1≤n,a,b≤105
Example
7 2 2
1010101
在该样例中,每个主要比赛都是 B O 3 BO3 BO3,也就是 3 局 2 胜制。主要比赛的每个次要比赛也是 B O 3 BO3 BO3。现在需要看看从这 7 个位置开始获取每场次要比赛的结果,先求得每场主要比赛的结果,再求整场比赛谁获胜。
从第一个位置开始比赛,
T
=
1010101
∣
1010101
…
T=1010101|1010101\dots
T=1010101∣1010101…,次要比赛的结果为 1:2 2:1 0:2 2:1 1:2...
即
10101
∣
10101
…
10101|10101\dots
10101∣10101…,主要比赛的结果为 1:2 1:2...
,即
00
…
00\dots
00…,也就是 A 队将以 2:1
获胜,结果为
1
1
1。
从第二个位置开始比赛,
T
=
0101011
∣
0101011
…
T=0101011|0101011\dots
T=0101011∣0101011…,次要比赛的结果为
01101
∣
01101
…
01101|01101\dots
01101∣01101…,主要比赛结果为
101
…
101\dots
101… 也就是 A 队将以 2:1
获胜,结果为
1
1
1.
继续从第三个位置开始比赛……直到从最后一个位置开始比赛。得到最终结果为 1110111 1110111 1110111。
Solution
将问题简化为这样的形式:
将循环串 T T T 以如下规则生成新循环串 T ′ T^\prime T′:
将串 T T T 展开。按照顺序在 T T T 上进行游戏。如果数字 0 0 0(或数字 1 1 1)的累计数量等于胜利条件 a a a ,则该小局的胜利方为 0 0 0(或 1 1 1),立即结束该小局游戏并开始新局。在这样生成的“胜利0-1串”上重复上述的游戏规则,当某个数字的累计数量等于胜利条件 b b b,则该大局结束,得到大局的胜利方。
稍微写点模拟代码演示:
for(int x=1;x<=n;x++)
{
int y=x;
int cnt[2]={0,0};
cnt[T[x]]++;
while(1)
{
if(cnt[0]>=a||cnt[1]>=a)// or >=b
{
if(cnt[0]>=b) cout<<0;
else cout<<1;
break;
}
y++;
if(y>n) y-=n;
cnt[T[y]]++;
}
}
这是一个嵌套问题。我们先考虑内层问题。
注意到上述代码中的 y
只会向右循环移动,可以用循环双指针在
O
(
n
)
O(n)
O(n) 时间复杂度内解决内层问题。
我们可以得到:在 x x x 处开始进行一个小局,游戏结束时获胜方为谁、结束之后下一个小局从哪里开始进行。
但是从小局的胜利情况推大局的胜利情况就不能用双指针如法炮制了。因为此时面对的不是一个循环串 T T T,而是一个内向基环树森林,我们需要将每个点作为起点跑双指针,这样会超时的。
考虑倍增。
我们定义 j u m p x , k jump_{x,k} jumpx,k 为从 x x x 开始,进行 2 k 2^k 2k 个小局之后,接下来会从何处继续游戏。定义 c n t x , k , 0 / 1 cnt_{x,k,0/1} cntx,k,0/1 为从 x x x 开始,进行了 2 k 2^k 2k 个小局中,A/B 队获胜的数量。
在上面,我们已经用双指针求出了每一个起点 x x x 开始一小局的胜利方(令 c n t x , 0 , 胜利方 = 1 cnt_{x,0,胜利方}=1 cntx,0,胜利方=1)和下一局的起点 j u m p x , 0 jump_{x,0} jumpx,0。由此预处理 k = 1 , 2 , 3 … k=1,2,3\dots k=1,2,3… 的情况。
现在对于每个起点 x x x,我们进行倍增。类似于倍增求 LCA,我们倍增地找到最远的大局结束点 y y y,使得这之间双方胜利数量都刚好小于 b b b。此时,再向后走一步( y = j u m p y , 0 y=jump_{y,0} y=jumpy,0)一定会导致这一大局结束,所以接下来的这一小局的获胜方就是从 x x x 开始玩一大局的获胜方了。
Code
#define N 100010
int n,a,b;
string s;
int jump[N][31];
LL cnt[N][31][2];
string ans;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
int t=1;
// cin>>t;
while(t--)
{
cin>>n>>a>>b;
cin>>s;
int l=0,r=0;
int cnt0[2]={0,0};
cnt0[s[0]-'0']=1;
while(1)
{
if(l>=s.size()) break;
if(cnt0[0]>=a||cnt0[1]>=a)
{
bool win=(cnt0[1]>=a);
cnt[l][0][win]=1;
jump[l][0]=r+1;
cnt0[s[l]-'0']--;
l++;
continue;
}
r++;
if(r>=s.size()) r-=s.size();
cnt0[s[r]-'0']++;
}
// for(int i=0;i<s.size();i++) cout<<cnt[i][0][0]<<" "<<cnt[i][0][1]<<endl;
for(int k=1;k<=30;k++)
{
for(int i=0;i<s.size();i++)
{
jump[i][k]=jump[jump[i][k-1]][k-1];
cnt[i][k][0]=cnt[i][k-1][0]+cnt[jump[i][k-1]][k-1][0];
cnt[i][k][1]=cnt[i][k-1][1]+cnt[jump[i][k-1]][k-1][1];
}
}
for(int i=0;i<s.size();i++)
{
int x=i;
int now[2]={0,0};
for(int k=30;k>=0;k--)
{
if(now[0]+cnt[x][k][0]<b&&now[1]+cnt[x][k][1]<b)
{
now[0]+=cnt[x][k][0];
now[1]+=cnt[x][k][1];
x=jump[x][k];
}
}
ans.push_back(cnt[x][0][1]+'0');
// if(now[0]==b) ans.push_back('0');
// if(now[1]==b) ans.push_back('1');
}
cout<<ans<<endl;
}
return 0;
}