博客园 首页 私信博主 显示目录 隐藏目录 管理 动画

Codewars. Naive subarray(分块 哈希)

题目链接

虽然退役了,但因为Codewars想升个级所以写了两个题,xs

这个一样的题,感觉还是挺妙的。


Description
给定长为n的序列Ai,求有多少个区间,满足区间中所有数的出现次数为奇数。
n2×105, Ai105

Solution
为每个数赋值一个随机权值vali。则区间所有数出现次数为奇数,等价于 区间所有权值的异或和 等于 区间出现过的权值的异或和。
i为当前计算区间的右端点,[j,i]所有权值异或和为fj[j,i]出现过的权值异或和为gj,以i为右端点的合法区间数为ji[fj=gj]。记lasxx上次出现过的位置。
那么每次枚举前缀[1,i]时,操作即为:对f1,...,fi异或valAi,对glasAi+1,...,gi异或valAi,求fj=gjfj xor gj=0的个数。
hj=fj xor gj,则操作即为:对h1,...,hlasAi异或valAi,求hj=0的个数。

所以就是:区间异或一个值,求区间等于0的数的个数。可以分块。
cnt[b][k]表示块b中数k的出现次数,则每次修改,对整块更新tag[b],对零散部分将改变的值从cnt[b]里删掉并更新;每次询问,对整块查cnt[b][tag[b]],对零散部分暴力查值为tag[b]的个数。

cnt[b]可以用map,也可以哈希(如果用哈希,在更新cnt[b]时不妨直接将整个cnt[b]哈希表清空。哈希表不太好删除)。
复杂度O(nnlogn)O(nn)


#include <bits/stdc++.h>
#define pc putchar
#define gc() getchar()
#define pb emplace_back

inline int read()
{
	int now=0,f=1; char c=gc();
	for(;!isdigit(c);c=='-'&&(f=-1),c=gc());
	for(;isdigit(c);now=now*10+c-48,c=gc());
	return now*f;
}

typedef long long LL;
typedef unsigned long long ull;
const int N=2e5+5,M=1e5+5,B=450,B_size=450;
const int mod=2099;

int las[M],bel[N],L[B],R[B];
ull val[M],seq[N],tag[B];
std::mt19937_64 Rand(19511016);

struct Hash
{
	int Enum,Time,time[mod+2],H[mod+2],nxt[B_size+2],cnt[B_size+2];
	ull to[B_size+2];

	inline void AE(int u,ull v,int t)
	{
		to[++Enum]=v, nxt[Enum]=H[u], cnt[Enum]=t, H[u]=Enum;
	}
	void Clear()
	{
		++Time, Enum=0;
//		memset(H,0,sizeof H);
	}
	void Init(int v)
	{
		Clear();
		Get_head(0), AE(0, 0, v);//注意在插入0前,更新0的表头!其实应该用Insert(0)去插入。
	}
	int Get_head(int x)
	{
		return time[x]==Time?H[x]:(time[x]=Time,H[x]=0);
	}
	int Query(ull v)
	{
		for(int i=Get_head(v%mod); i; i=nxt[i])
			if(to[i]==v) return cnt[i];
		return 0;
	}
	void Insert(ull v)
	{
		for(int i=Get_head(v%mod); i; i=nxt[i])
			if(to[i]==v) return void(++cnt[i]);
		AE(v%mod, v, 1);
	}
}cnt[B];

void Update(int p,ull v)
{
	if(!p) return;
	int b=bel[p];
	for(int i=1; i<b; ++i) tag[i]^=v;
	
	cnt[b].Clear();
	for(int i=L[b]; i<=p; ++i) cnt[b].Insert(seq[i]^=v);
	for(int i=R[b]; i>p; --i) cnt[b].Insert(seq[i]);
}
int Query(int p)
{
	int b=bel[p], ans=0; ull v=tag[b];
	for(int i=1; i<b; ++i) ans+=cnt[i].Query(tag[i]);
	for(int i=L[b]; i<=p; ++i) ans+=(seq[i]==v);
	return ans;
}
template<std::size_t S>
LL solve(std::array<int,S> A)//注意A下标从0开始!
{
	int n=A.size();
	for(int i=1; i<=n; ++i) seq[i]=0, las[A[i-1]]=0;
	for(int i=1; i<=n; ++i) if(!val[A[i-1]]) val[A[i-1]]=Rand();

	for(int i=1; i<=n; ++i) bel[i]=(i-1)/B_size+1;
	int tot=bel[n];
	for(int i=1; i<=tot; ++i) L[i]=(i-1)*B_size+1, R[i]=i*B_size;
	R[tot]=std::min(R[tot], n);

	for(int i=1; i<=tot; ++i) tag[i]=0, cnt[i].Init(R[i]-L[i]+1);

	LL ans=0;
	for(int i=1; i<=n; ++i)
		Update(las[A[i-1]],val[A[i-1]]), ans+=Query(i), las[A[i-1]]=i;
	return ans;
}

int main()
{
	std::array<int, 27> A2 = {6,1,7,4,6,7,1,4,7,1,4,6,6,7,4,1,6,4,7,1,4,5,3,2,1,6,9};
	printf("%lld\n",solve(A2));//114
	std::array<int, 15> A = {2, 5, 2, 3, 6, 7, 8, 23, 23, 13, 65, 31, 3, 4, 3};
	printf("%lld\n",solve(A));//53
	

	return 0;
}
posted @   SovietPower  阅读(234)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
历史上的今天:
2018-09-11 BZOJ.4939.[Ynoi2016]掉进兔子洞(莫队 bitset 分组询问)
2018-09-11 NOIp模拟赛 巨神兵(状压DP 容斥)
2018-09-11 NOIp模拟赛 现实(DP 拓扑)
点击右上角即可分享
微信分享提示