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;
}

 

posted @ 2023-03-31 17:41  HHHG  阅读(23)  评论(0编辑  收藏  举报