Codeforces Round 962(div 3) E Decode(解码)

题目链接 :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;
}

posted @   chhh31  阅读(92)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示