题意:多组测试,分别给出一串字符串,对于每一个范围[l,r]求出贡献并相加,贡献就是这个范围里面的小范围[x,y]有多少个,要求从x到y的字符串里面的0和1的数量相同。最后输出贡献。
思路:最直接的思路就是去枚举这个字符串的全部字串,再在这些子串里面枚举全部的字串,如果字串的字串0和1的数量相同,那么字串的贡献就增加一,但是这样肯定会超时。
透过现象看本质,可以发现对于每一个满足条件的[x,y],[x,y]包含在[x,y],[x+1,y],[x+1,y+1]....这些区间里面,并且每一个区间都会为答案提供一个贡献,所以这个区间能够给答案提供的贡献是x*(字符串长度-y+1),那么答案就被转换为了全部符合条件的[x,y]的数量*x*(字符串长度-y+1)。
那么可以先使用前缀和来求出全部的符合条件的区间,当字符为1的时候增加1,当字符为0的时候减少1,那么如果二个位置的前缀和相同那么他们的0和1的数量就一定是相同的。但是直接的找到符合条件的区间的时间复杂度是不行的,所以可以先使用map<int,vector<int>> 来记录前缀和相等的位置,并且这样存下来的字符串是一个大串的,例如:010101,前缀和是-1,0,-1,0,-1,0,map[-1]里面存的值就是1,3,5,意思就是2到3,4到5是符合条件的字符串,而且可以发现2到5也是符合条件的,这样一来可以更快的算出这类串的贡献(定义是一类串那么它们的前缀和的值就是相同的)。可以开一个再开一个前缀和来记录以当前位置为x那么左边可以取l的位置有多少个,这样就可以线性的求出一类串的全部贡献。为什么需要开前缀和来记录呢?可以从上面的例子中发现5这个位置的x可以是4,也可以是2,那么这个位置为y的贡献就是2*4+2*2=2*(4+2),这个4+2就是以5为y之后l可以取的位置数,2是r可以取的位置数。因为前缀和的性质,所以记录的点其实是符合条件的串的x-1位置,所以计算前缀和的时候需要+1。
//冷静,冷静,冷静
//调不出来就重构
#pragma GCC optimize(2)
#pragma GCC optimize("O3")
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<ll,ll> pii;
const int N=1e6+10,mod=1000000007;
ll pre[N];
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
ll t;cin>>t;
while(t--)
{
string s;cin>>s;s=' '+s;
map<ll,vector<ll>> op;
op[0].push_back(0);
//因为只要前缀和数组出现0,那么就可以和第一个位置构成一个[x,y]
for(int i=1;i<s.size();i++)
{
pre[i]=pre[i-1]+(s[i]=='1')-(s[i]=='0');
op[pre[i]].push_back(i);//记录前缀和相同的位置
}
ll ans=0;
for(auto it:op)
{
vector<ll> p;
vector<ll> add(it.second.size()+10);
p.push_back(0);//增加0,为了后面的前缀和方便
for(auto v:it.second)
{
p.push_back(v);
}
for(int i=1;i<p.size();i++)
{
add[i]=add[i-1]+p[i]+1;
//含义是当前位置左边可以取多少个l
}
for(int i=1;i<p.size();i++)
{
ans=ans+(s.size()-p[i])*add[i-1]%mod;
// s.size()-p[i]代表以当前位置为y,那么r可以取多少个,i是右端点,所以因该使用add[i-1]代表l可以取多少个
ans%=mod;
}
}
cout<<ans<<endl;
}
return 0;
}