20230329
D题-三天前(Three Days Ago)
题意
给你一行仅由数字(0~9)构成的串,计算满足“条件”的子串的数量。
条件是:该数字串可以拆分成两个「由相同数量的对应数字」构成的的数字串。
串的最大长度为5*105
思路
如何判断一个数字串是符合“条件”的呢?举几个例子:
07211270 符合, 00772211 符合, 70721120 符合, 77722211 不符合,但 7722 符合
可以发现,只要一个串中的数字出现偶数次,那么它就是符合条件的。
那么如何知道一个串中各个数字的出现次数呢?考虑用桶记录。
开一个二维数组 ton[500005][10], ton[ i ][ j ] 表示 1 ~ i 的数字串中数字 j 的出现次数。
对于每一个从头开始的数字串,只要对应数组中每个数字的桶中均为偶数,那么符合条件。
那么不从头开始的数字串呢,很简单,类比前缀和,只要用末尾的数量减去开头的数量就可以了。
组合数算出想要枚举所有的字串需要 2n-1 次,明显TLE
如何简化呢?从「不从头开始的数字串」继续分析,对于满足条件的数字串来说,因为偶数一定来自于两个奇数或是两个偶数相减,所以其 末尾处的数组与开头处的数组的各个数字的桶 中奇偶相同。只要找出所有满足该要求的数组,再运用组合数学就能找出所有的满足条件的数字字串。
处理「从头开始的字符串」也很简单,把它当做 开头处的数组是各个数字的桶 中全为偶数 的数字串就行啦。
可是每次判断 各个数组的各个数字的桶 中的奇偶实在太麻烦了,发现数字只有十个且仅有奇数/偶数两个状态。可以采用状态压缩的思路,用一个二进制数储存 10 个数字的奇偶状态,第 i 位表示 “i-1” 数字的奇偶。这样用一个一维数组 ton[500005] 就能表示某处各个数字出现次数的奇偶。
处理时将数组排序使相同的数聚在一起(包括表示开头处的数),num 记录相同的数的数量, num*(num-1) 即为对应的满足条件的数字字串的数量。
一些细节
读入时利用异或(按位加)的性质即可。
考虑所有数都相同的最坏情况,答案应开 long long.
代码如下:
#include<bits/stdc++.h>
using namespace std;
string ch;
int ton[500005];
int two[15];
inline void meta()
{
two[0]=1;
for(int i=1;i<=9;i++)
{
two[i]=two[i-1]<<1;
}
}
int main()
{
meta();
cin>>ch;
memset(ton,0,sizeof(ton));
int str=ch.size();
for(int i=1;i<=str;i++)
{
ton[i]=ton[i-1]^two[ch[i-1]-'0'];
}
sort(ton+1,ton+str+1);
long long num=1,ans=0;
for(int i=1;i<str;i++)
{
if(ton[i]==ton[i-1])
{
num++;
}
else
{
ans+=(num*(num-1)/2);
num=1;
}
}
if(ton[str]==ton[str-1])
{
num++;
ans+=(num*(num-1)/2);
}
else
{
ans+=(num*(num-1)/2);
}
printf("%lld",ans);
return 0;
}