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