CF2070E(edu.175) Game with Binary String 题解
题意:
题目描述较为繁琐,vj上有翻译版:点这里
思路:
首先可以确定的是,如果玩家 \(2\) 的策略是最优策略,那么其一定会选形如 \(01/10\) 这样的串。因为玩家 \(2\) 必须要选至少一个 \(1\) ,那么一定我们希望每次尽量少用 \(1\) 并且多消耗玩家1需要的 \(0\),这样才可以使得可以撑更多的回合,更优。
又发现,我们如果想让玩家 \(1\) 获胜,那么必然其比玩家 \(2\) 多玩了 \(1\) 轮。考虑玩家 \(2\) 输了最后会出现怎样的局面:
显然有两种情况。第一种是所有 \(1\) 都被拿完了,并且局面还能保证玩家 \(1\) 至少还能玩 \(1\) 轮,第二种是最后还剩一个 \(1\) ,但是没有其他任何元素了,那么此时玩家 \(2\) 也无法胜利。
所以我们考虑记 \(cnt_{0/1}\) 表示序列中 \(0/1\) 的个数,先考虑上述第一种情况,此时玩家 \(2\) 拿完了所有 \(1\) ,并且按照最优策略。那么玩家 \(2\) 就拿了 \(cnt_1\) 个 \(1\) 和 \(0\),此时若要满足玩家 \(1\) 能至少比玩家 \(2\) 多玩一轮,则一定,其拿了 \(2\times(cnt_1 + 1)\) 个 \(0\)。也就等价说明了,要满足玩家 \(1\) 必胜,一定要满足:
什么,你说怎么保证每一次玩家 \(1\) 都可以行动?很简单,我们换一个视角思考这个问题,考虑我们现在已经有了 \(cnt_1\) 个 \(1\),我们将所有的 \(0\) 分配到任意的两个 \(1\) 之间,因为至少有 \(3\times cnt_1+2\) 个 \(0\) 则所有的分配情况下都一定会存在至少有 \(3\) 个连续(相邻)的 \(0\) \((cnt_1>0)\) 的方案,所以不用担心不够用,并且每一轮玩家 \(1\) 和玩家 \(2\) 都进行了游戏后,这个不等式是依然成立的,所以每一次都可以保证玩家 \(1\) 可以行动。
又很显然的,当上述不等式取等时,我们的必胜策略已经到极限了,再让 \(cnt_0\) 变小就无法保证玩家 \(1\) 必胜了,所以上述不等式是充要的。
用同样的分析方法,可以得出如果要产生第二种必胜结局,则一定:
证明思路同上,略。
现在考虑统计答案,我们试着枚举区间左端点 \(r\),那么我们现在需要知道的就是有多少个 \(l\) 是合法的。显然一个合法的 \(l\) 需要满足的充要条件(情况1)是:
我们试着对式子进行变换:
你发不等式现左边括号中的内容与不等式右边是同构的。所以我们定义函数 \(f(i)=cnti_0-3\times cnti_1\) ,所以不等式变为:
那么我们现在只需要找出这样的 \(l\) ,如果我们暴力枚举的话,复杂度是 \(O(n^2)\) 的,显然无法通过本题,但你发现这是一个求区间 \([1,r-1]\) 中的合法下标的问题,既然是区间问题,我们可以考虑建立一颗树状数组来解决这个问题,以减少对前面区间的重复遍历。但是这不是一般的树状数组。事实上,我们应当建立一颗值域树状数组,也就是下标为 \(f(x)\) 中存的东西。并且每次遍历完 \(r\) 后,都需要将树状数组中 \(f(r)\) 的部分加\(1\),即遍历到了多一个值为 \(f(r)\) 的下标 \(r\) ,方便给后面的数计算。
对于情况二是同理的,只是需要注意的是,情况二是等号,所以需要减去小于 \(f(r) + 1\) 的部分的贡献(这么说有点抽象,直接看代码可能更好理解)。
Code:
注意上述分析中 \(f(x)\) 可能为负数,所以需要加一个偏移量来保证树状数组下标的非负。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
inline int read(){
char c=getchar();bool f=0;int x=0;
while(c > '9' || c < '0') f|=c=='-',c=getchar();
while(c >= '0'&&c <= '9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
if(f) x=-x;return x;
}
const int M = 4e6 + 10,N = 3e5 + 10;
char s[N];
int n,tree[M],pre[N],up_limit;
int lowbit(int x){return x & (-x);}
void add(int x, int k){
for(;x <= up_limit;x += lowbit(x))
tree[x] += k;
}
LL query(int x){
LL res = 0;
for(;x;x -= lowbit(x))
res += tree[x];
return res;
}
int main()
{
n = read();
scanf("%s",s + 1);
LL ans = 0;
up_limit = 3e6 + 10;
for(int i = 1;i <= n;++i)
{
pre[i] = pre[i - 1] + (s[i] == '0' ? 1 : -3);
}
for(int i = 0;i <= n;++i) pre[i] += 3 * n + 5;
add(pre[0],1);
for(int i = 1;i <= n;++i)
{
ans += query(pre[i] - 2) + (query(pre[i] + 1) - query(pre[i]));
add(pre[i],1);
}
printf("%lld",ans);
return 0;
}