题目链接 :https://codeforces.com/contest/1996/problem/E
题目描述:
为了获得你最喜欢的角色,你不惜一切代价,侵入了游戏的源代码。经过几天的努力,你终于找到了编码游戏 gacha 系统的二进制字符串。为了解码它,你必须首先解决以下问题。给你一个长度为n的二进制字符串s。对于每对整数(l,r) (1≤l≤r≤n),计算子字符串Sx,Sx+1,Sx+2,...,Sy中 0的数量等于1的数量的对数(x,y)(l≤x≤y≤r),输出所用可能的(l,r)模数10^9 + 7
输入
第一行包含 t ( 1≤t≤1000) — 测试用例的数量。每个测试用例包含一个二进制字符串 s( 1≤|s|≤2⋅105)。保证 s仅包含字符 0和 1。保证所有测试用例的 |s|之和不超过 2⋅10^5第一行包含 t ( 1≤t≤1000) — 测试用例的数量。每个测试用例包含一个二进制字符串 s( 1≤|s|≤2⋅10^5)。保证 s仅包含字符 0和 1。保证所有测试用例的 |s|之和不超过 2⋅10^5
输出
对于每个测试用例,输出一个整数,答案模 109+7 。
题目分析:
首先,题意让我们找满足0的数量等于1的数量的每一个子集,很容易想到暴力的做法是,直接枚举所有子集,判断子集是否满足要求。但是,这样做就需要四层循环,分别是先确定l和r,然后再在l和r区间找子集,也就是枚举x,y这就是这四层循环。直接用暴力肯定是过不了的,所有我们要另寻他发。这题有”三典“:
1.放眼整个字符串,把字符0看成-1,字符1看成1,方便算满足条件的区间
2.经过1操作处理后的数组的前缀和(存放在数组arr中)
3.改变思考的角度,不从l,r这个大区间找x,y这个小区间,直接找x,y,然后求x,y能在多少个l,r里面出现,也就是求x,y区间对答案的贡献
经过这三个操作,我们很容易得出,假如arr[y] == arr[x - 1]就代表x,y区间满足条件,然后算贡献,既然这个贡献是看x,y在多少个l,r区间里面出现,这个区间首先是得包含x,y吧,那么,x左边有几个数能选加一乘以y右边有几个数能选加一就是所有包含x,y的l,r区间,为啥要加一呢?因为左边的选择有选一个选两个,也有不选这个选项,所以要加一,用数学式子来讲,就是区间数为x * (s.size() - y + 1)(x从1开始)。从这里看问题已经简化一半了,只用求前缀和,然后枚举x,y算贡献就行,但是还是有两层循环去计算答案,这个还能简化。假设我们正在枚举x,y区间,从右往左算,y循环是外层,x是里层,假设字符串是01010101,那么前缀和就是-1,0,-1,0,-1,0,-1,0,假设y是倒数第二个数,也就是y == 7,arr[y] == -1。这个时候让x往左动,第一个满足的x,y区间是x == 6,arr[x - 1] == arr[y],贡献是7 * 6。然后第二个x是 x == 4,贡献7 * 4,第三个是x == 2,合并多项式7 * (6 + 4 + 2),也就是y等于7时所有满足条件的x的坐标之和。从右向左也是同理。然后,我们能用可以用x边从左向右遍历前缀和数组,边找这个区间之前符合要求的x坐标的和,我们可以用map<int,long long> m来存,在到某一前缀和值arr[y]的时候能访问y之前所有满足条件的x的和。然后更新答案ans += m[arr[y]] * (n - y + 1),更新后也要更新相应的满足条件的x坐标的和。这样只需要一层循环去计算贡献。
只有前缀和跟求贡献的TLE代码:
#include <iostream>
#include <algorithm>
#include <string>
#define ll long long
using namespace std;
const int N = 200010;
int arr[N];
constexpr int P = 1e9 + 7;
void solve()
{
string s;
cin >> s;
int len = s.size();
for (int i = 0; i < len; i++)
{
if (s[i] == '0')
{
arr[i + 1] = -1;
}
else if (s[i] == '1')
{
arr[i + 1] = 1;
}
arr[i + 1] += arr[i];
}
ll ans = 0;
for (int i = 1; i <= len; i++)//两层循环求答案,最后超时了
{
int j = i + 1;
while (j <= len)
{
if (arr[j] == arr[i - 1])
{
ans += (i * (len - j + 1)) % P;
}
j++;
}
}
cout << ans % P << endl;
}
int main()
{
int tNum;
cin >> tNum;
for (int i = 0; i < tNum; i++)
{
solve();
}
return 0;
}
正确code:
#include <iostream>
#include <algorithm>
#include <string>
#include <map>
#define ll long long
using namespace std;
const int N = 200010;
const long long P = 1e9 + 7;
int arr[N];
void solve()
{
string s;
cin >> s;
int len = s.size();
for (int i = 0; i < len; i++) //作前缀和,如果坐标对应的字符是'0'那么代表-1,如果是'1'那么代表+1
{
arr[i + 1] = arr[i];
if (s[i] == '0')
{
arr[i + 1] += -1;
}
else if (s[i] == '1')
{
arr[i + 1] += 1;
}
}
ll ans = 0;
map<int, long long> m;
m[0]++; //这个m[0] ++是因为当出现第一个前缀和为0时m[0] = 0那样就会变成0 * (n - y + 1),正确的应该是
//1 * (n - y + 1),所以要加成一
for (int i = 1; i <= len; i++)
{
ans += (len - i + 1) * m[arr[i]] % P; //y乘满足条件x坐标之和,因为题目数据范围大于10^9 + 7所以ans加上的值
//可能大于10^9 + 7所以要提前模上
m[arr[i]] += i + 1; //更新满足条件x坐标之和
}
ans %= P;//后面也可能会大于10^9 + 7
cout << ans << endl;
}
int main()
{
int tNum;
cin >> tNum;
for (int i = 0; i < tNum; i++)
{
solve();
}
return 0;
}
标签:arr,前缀,962,int,Codeforces,Decode,测试用例,区间,include
From: https://www.cnblogs.com/chhh31/p/18331196