Consider the following game. Two players have a binary string (a string consisting of characters 0 and/or 1). The players take turns, the first player makes the first turn. During a player’s turn, he or she has to choose exactly two adjacent elements of the string and remove them (the first element and the last element are also considered adjacent). Furthermore, there are additional constraints depending on who makes the move:
The player who can’t make a valid move loses the game. This also means that if the string currently has less than 2 2 2 characters, the current player loses the game.
You are given a binary string s s s of length n n n. You have to calculate the number of its substrings such that, if the game is played on that substring and both players make optimal decisions, the first player wins. In other words, calculate the number of pairs ( l , r ) (l, r) (l,r) such that 1 ≤ l ≤ r ≤ n 1 \le l \le r \le n 1≤l≤r≤n and the first player has a winning strategy on the string s l s l + 1 … s r s_l s_{l+1} \dots s_r slsl+1…sr.
Input
The first line contains one integer n n n ( 1 ≤ n ≤ 3 ⋅ 1 0 5 1 \le n \le 3 \cdot 10^5 1≤n≤3⋅105).
The second line contains the string s s s, consisting of exactly n n n characters. Each character of the string is either 0 or 1.
Output
Print one integer — the number of substrings such that, if the game is played on that substring, the first player wins.
Example
Input
100010010011
Output
12
在这道题目中,我们有一个二进制字符串,游戏规则如下:
有两个玩家轮流进行操作,第一个玩家先手。
每一轮,玩家需要选择一个二进制字符串的相邻两位,并将其移除。
如果当前玩家无法进行合法的操作,即字符串的长度小于 2 或者不能满足当前玩家的条件,则当前玩家输掉游戏。
我们需要计算在所有可能的子字符串中,第一位玩家能获胜的子字符串数。
输入格式:
n
,表示字符串的长度。s
,长度为 n
。输出格式:
输出一个整数,表示第一位玩家能获胜的子字符串数。
为了有效地解决这个问题,我们需要使用一些优化的技巧,避免暴力枚举所有子字符串。通过观察题目,发现游戏的状态由字符串的0和1的分布决定,因此可以通过前缀和与树状数组来优化解法。
首先,我们修改游戏规则:玩家可以移除任意类型的字符对,而不仅仅是相邻字符。第一个玩家移除两个 0
,第二个玩家移除一个 0
和一个 1
(如果没有 0
,则移除两个 1
)。每次操作会移除一个 1
和三个 0
。
假设字符串长度能被 4 整除,那么当 c 0 = 3 c 1 c_0 = 3c_1 c0=3c1 时,游戏持续到字符串被移除,第一个玩家输;如果 c 0 < 3 c 1 c_0 < 3c_1 c0<3c1,第一个玩家先没操作可做;如果 c 0 > 3 c 1 c_0 > 3c_1 c0>3c1,第二个玩家先没操作可做。所以,第一个玩家获胜的条件是 c 0 > 3 c 1 c_0 > 3c_1 c0>3c1。
如果长度不能被 4 整除,情况会根据长度的余数不同而变化。通过推导,对于每个余数 i i i,可以得出类似条件:如果 c 0 > 3 c 1 + k i c_0 > 3c_1 + k_i c0>3c1+ki,第一个玩家获胜,其中 k i k_i ki 依赖于余数。可以通过动态规划或分析推导出这些常数。
为解决问题,我们将 1
替换为 -3
,0
替换为 1
,并计算区间和 s u m [ l : r ] > k i sum[l:r] > k_i sum[l:r]>ki。然后使用前缀和技巧和数据结构(如树状数组、线段树)来计算满足条件的子数组个数,时间复杂度为 O ( n log n ) O(n \log n) O(nlogn)。
该方法通过维护前缀和并查找满足条件的子区间,得出最终结果。
前缀和:为了快速计算每个子字符串中0的数量,我们使用前缀和数组 sp[i]
,表示从 s[1]
到 s[i]
中1的个数。通过这个前缀和,我们可以快速判断某个区间内的0和1的分布。
树状数组:通过树状数组,我们可以高效地维护状态并快速查询与更新。
S
,用于记录不同状态下的更新和查询。sp[i]
来表示当前状态,从而推导出不同子字符串能否满足第一位玩家获胜的条件。#include
#define endl '\n'
#define int long long
#define BoBoowen ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
using namespace std;
constexpr int NMAX = 300005;
struct aib
{
int sum[NMAX * 4];
void update(int pos, int x)
{
for (pos += NMAX * 3; pos < NMAX * 4; pos += pos & -pos)
sum[pos] += x;
}
int query(int pos)
{
int s = 0;
for (pos += NMAX * 3; pos; pos &= pos - 1)
s += sum[pos];
return s;
}
};
int N;
char s[NMAX];
int sp[NMAX];
aib S[4];
void solve()
{
int i, x;
int ans = 0;
scanf("%d", &N);
fgets(s, NMAX, stdin);
fgets(s, NMAX, stdin);
for (i = 0; i < N; ++i)
sp[i + 1] = sp[i] + s[i] - '0';
for (i = 0; i < N; ++i)
{
x = i - 4 * sp[i + 1];
ans += S[i % 4].query(x - 1);
ans += S[(i + 3) % 4].query(x);
ans += S[(i + 2) % 4].query(x + 4);
ans += S[(i + 1) % 4].query(x);
x = i - 4 * sp[i];
S[i % 4].update(x, 1);
}
cout << ans;
}
signed main()
{
solve();
}
树状数组(aib结构):
aib
结构体用于维护一个树状数组。该数据结构支持 update
和 query
操作。update
用于更新某个位置的值,query
用于查询某个位置的前缀和。前缀和:
sp[i]
表示字符串 s
中前 i
个字符中1的数量。通过 sp
可以高效地计算任意子串中0和1的数量。计算结果:
i
,通过查询和更新树状数组来计算当前子字符串是否满足第一位玩家获胜的条件,并统计满足条件的子字符串数量。树状数组的索引变换:
NMAX * 3
到 NMAX * 4
,这是为了避免负数的索引。O(n log n)
,因为每次更新和查询的时间复杂度是 O(log n)
,而总共有 n
次更新和查询。通过结合前缀和与树状数组,我们可以高效地解决这道题目。树状数组提供了快速的查询和更新操作,而前缀和帮助我们快速获取子字符串的0和1的分布,从而使得整个解法可以在 O(n log n)
时间复杂度内完成。