Gym - 101350A Sherlock Bones

题目链接:https://cn.vjudge.net/problem/Gym-101350A

题意:给你一个只含01的字符串str 规定一个函数F(i,j) 表示闭区间【i,j】内1的个数  求有多少个三元组(i,j,k) i < j < k

使得F(i,j)== F(j,k)且str[j] == 1  

字符串最长为2e5

思路:组队赛的时候场上没出的题,当时的思路往维护前缀和上靠来着 但是思维江化 没有想清楚 赛后看了题解静下来想了想 

才明白这个题的巧妙之处

首先我们要确定下来的是若存在区间 [i,k] 满足 F(i,j)== F(j,k)且str[j] == 1   那么这个区间必定有两个性质

(1) [i,k]之间1的个数必定为奇数个 若为偶数个 则无法满足str[j]==1 且 [ i ,j ) 与 ( j , k ] 区间中1的个数相同

(2) (i,k)之间必有至少一个1  若(i,k)之间1的个数为0 则无法满足 str[j]==1

根据以上两个性质 我们可以得到以下推论:

若存在一个闭区间[i,k]  在这个闭区间内1的个数为奇数个 且在开区间(i,k)存在至少一个1  那么必有唯一一个j 

 满足 i < j < k 且 F(i,j)== F(j,k) 且 str[j] == 1  

根据以上推论 原问题就变成了 找到所有满足以下条件的区间:闭区间[i,k]有奇数个1 且 开区间(i,k)内有一个1 

如何找到所有满足条件的区间?

1、记录字符串前缀和pre[i]  表示1~i之间1的个数

2、记录dp[j][1] 表示 j ~ n 之间有几个奇区间(j ~ n-1 j ~ n-2 j~n-3 .... j ~ j+1 这些区间中奇区间的个数)

dp[j][0] 表示 j ~ n 之间有几个偶区间(j ~ n-1 j ~ n-2 j~n-3 .... j ~ j+1 这些区间中偶区间的个数)

转移方程如下:

如果第j位是1 那么他之后所有的偶区间都将转化为奇区间 之前所有的奇区间都将转化为偶区间 且第j位本身是奇区间

所以

                dp[i][1] = dp[i+1][0] + 1;
                dp[i][0] = dp[i+1][1];

如果第j位是0 那么他之后所有的奇区间还是奇区间 之前所有的偶区间还是偶区间 第j位本身是偶区间

所以

                dp[i][0] = dp[i+1][0] + 1;
                dp[i][1] = dp[i+1][1];

3、记录离i最近的1的位置 记录为_j

这是很关键的一步 如果不记录离i最近的1的位置 就会造成100这样的区间被判断为满足条件的区间

4、枚举i 只要每次[i , _j] 与 [_j+1,k] 两个区间的1的个数和为奇数个 就存在一个满足条件的[i,k]

但问题是我们如何得到所有满足条件的k呢 我们没有对k进行枚举也没有对k进行记录啊 

事实是满足条件的k其实根本不需要我们枚举 因为我们已经用dp[j][1] 和 dp[j][0]记录了j后面的所有奇区间和偶区间

也就是说 满足条件的k的个数其实就是dp[j][1]与dp[j][0]的值

如果[i,_j]为偶区间 那么后面必定有dp[_j+1][1]个k可以和i匹配

如果[i,_j]为奇区间 那么后面必定有dp[_j+1][0]个k可以和i匹配

我们每次只需要将他们累加即可得到最后的结果 需要注意的是 我们从后往前枚举i所以每次i都是不同的 这也就保证了每次得到的

三元组i,j,k一定不会重复

正确性与非重性证毕

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
using namespace std;
long long dp[200010][5];
long long pre[200010];
int main()
{
    std::ios::sync_with_stdio(false);
    int t;
    cin >> t;
    string str;
    int n;
    while(t--)
    {
        memset(dp,0,sizeof(dp));
        memset(pre,0,sizeof(pre));
        cin >> n;
        cin >> str;
        for(int i = 0; i < n; i++)
        {
            pre[i+1] += pre[i] + (str[i]-'0');
        }
        str = "0" + str;
        for(int i = n; i >= 1; i--)
        {
            if(str[i]=='1')
            {
                dp[i][1] = dp[i+1][0] + 1;
                dp[i][0] = dp[i+1][1];
            }
            else
            {
                dp[i][0] = dp[i+1][0] + 1;
                dp[i][1] = dp[i+1][1];
            }
        }
        long long ans = 0;
        bool flag = false;
        int _j = n + 1;
        for(int i = n; i >= 1; i--)
        {
            if(!flag)
            {
                if(str[i]=='1')
                {
                    flag = true;
                    _j = i;
                }
            }
            else
            {
                if((pre[_j]-pre[i-1])%2==0)
                {
                    ans += dp[_j + 1][1];
                }
                else
                    ans += dp[_j + 1][0];
                if(str[i]=='1')
                {
                    _j = i;
                }
            }
        }
        cout<<ans<<endl;
    }
}

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值