#C241001C. 简单计数


#C241001C. 简单计数

标签(Label)

  • 哈希
  • 线段树

网址(Website)

题目详情 - 简单计数 - Super

题解 - 简单计数 - Super

题目(Problem)

详情如下

题目描述

小 C 有一个长度为 $n$ 的序列 $a$,他定义区间 $[l, r]$ 是好的,当且仅当区间 $[l, r]$ 内的每个元素的出现次数都恰好为 $k$。

请求出所有的好区间的数量。

输入格式

从文件 count.in 中读入数据,每组数据包含多个测试点。

每个测试点的第一行包含两个正整数 $n, k$,表示序列长度和要求的元素出现次数。

接下来一行包含 $n$ 个正整数,代表序列 $a$。

输出格式

输出到文件 count.out 中。

对每一个测试点,输出一行一个正整数,代表好区间的数量。

样例

输入数据 1

1
10 2
3 1 2 2 1 3 3 1 2 1

输出数据 1

8

【样例1解释】

区间 $[3,4], [6,7], [2,5], [5,8], [1,6], [2,7], [3,8], [4,9]$ 都是好区间。

【输入输出样例2】

见选手目录下的 count/count2.incount/count2.ans

数据规模与约定

对于所有数据,有 $1 \leq \sum n \leq 2 \times 10^5, 1 \leq a_i \leq 10^9, 1 \leq k \leq n$。

测试点$\sum n \leq$特殊性质1特殊性质2
$1 \sim 4$$2 \times 10^3$
$5 \sim 7$$2 \times 10^5$$k = 2$
$8 \sim 10$$2 \times 10^5$$k = 3$$a_i \leq 20$
$11 \sim 20$$2 \times 10^5$

题解(Solution)

$\qquad$我采用的是 $\text{Hash}$ 做法。

$\qquad$首先将每一种颜色离散化,并且对于每个颜色,钦定这个颜色对应的 $\mathrm{Hash}$ 值(建议打双哈希),然后从前往后扫,算出每个位置的前缀 $\mathrm{Hash}$ 值,同时记录每个数出现的次数,每当这个次数是 $k$ 的整数倍时,就在前缀 $\mathrm{Hash}$ 值中减去当前这个数的这 $k$ 次贡献,容易发现,如果对于一个合法区间,如果前缀 $\mathrm{Hash}$ 相等,这个区间里面所有的元素个数一定都分别是 $k$ 的整数倍,那我们只需要判断这个区间里面每种元素的出现次数刚好是 $k$ 次而不是 $k$ 的倍数次就好了,这也是比较好处理的,记录每一个 $a[i]$ 在数组中出现的位置,记录当前位置往前数 $k$ 个相同的数后的第一个位置,所有在这个位置前面的位置都不能和 $i$ 这个位置组成区间。

$\qquad$时间复杂度 $O(n\log n)$ 。

算法分析

对于 $a_i$,记向后和它数值相同的第 $k-1$ 个数的下标为 $l$,第 $k$ 个数的下标为 $r$,则我们记以数字 $a_i$ 作为区间内该数值的开头的合法右端点区间为 $[l, r+1]$。

若区间 $[L, R]$ 合法,则对于每一种数值的开头位置 $k$,必须满足 $R$ 在 $k$ 所定义的合法区间内。

在左端点固定的条件下,我们可以看出区间 $[L, R]$ 合法的充要条件:

  • $[L, R]$ 内的数值种类数必须与包含 $R$ 的合法区间数目相同(每一种数值只能产生一个合法区间)

考虑找到每一个位置上的数作为开头数值时的合法区间,可以倒序枚举,用 $\text{map}$ 套 $\text{vector}$ 存储每一个数值出现的位置,若加入位置 $i$ 的数满足 $\text{vector}$ 长度 $\geq k$,则可以直接求出对应的合法区间 $[l, r]$。

考虑枚举区间左端点 $i$,那么一种暴力算法是找出 $[L, n]$ 区间内所有数值的开头位置,并对于每一个开头位置 $i$,将 $[i, n]$ 区间 $+1$,代表以这个区间内某个数作为右端点 $R$ 时数值种类数会多 $1$。同时将对应的合法区间 $[l, r]$ 区间 $-1$,则上述充要条件等价于位置 $R$ 的数值为 $0$。可以利用线段树维护区间加法。

上面算法的瓶颈在于每一次要重新找数值的开头位置,倒序枚举左端点,用 $\text{map}$ 套 $\text{vector}$ 存储每一个数值出现的位置,若 $\text{vector}$ 长度不小于 $2$,则撤销上一次的区间修改操作,复杂度均摊 $O(n \log n)$。

代码(Code)

#include<bits/stdc++.h>
#include<vector>
#include<map>
#include<queue>
#define For(i,l,r) for(int i=l;i<=r;i++)
#define Rof(i,l,r) for(int i=l;i>=r;i--)
using namespace std;
#define P pair<int,int>
#define int long long
#define x first
#define y second
inline int rd(){
	char c;bool f=false;while(!isdigit(c=getchar()))f=c=='-';int x=c^48;
	while(isdigit(c=getchar())){x=(((x<<2)+x)<<1)+(c^48);}return f?-x:x;
}const int inf = 0x3f3f3f3f3f3f3f3f;
const int N = 501234;
bool ST;
inline P operator+(P a,P b){return {a.x+b.x,a.y+b.y};}
inline P operator-(P a,P b){return {a.x-b.x,a.y-b.y};}
inline P operator*(int a,P b){return {a*b.x,a*b.y};}

struct HASH{
	int mod1=1e9+9,mod2=998244353;
	int bas1=1331,bas2=997;
	int a[N],b[N];
	void init(){a[0]=b[0]=1;For(i,1,2e5) a[i]=a[i-1]*bas1%mod1,  b[i]=b[i-1]*bas2%mod2;}
	inline P to(int x){return {a[x],b[x]};}
}H;

map<P,vector<int>> g;
int n,k,a[N];

map<int,int> mp;int tot;
vector<int> buc[N];
int lst[N],ans;
void calc(vector<int> v){
	queue<int> q;
	for(auto i:v){
		while(q.size() && q.front() < lst[i]) q.pop();
		ans += q.size(), q.emplace(i);
	}
}

void Solve(){
	mp.clear(),g.clear();
	ans = tot = 0;
	For(i,1,n){
		buc[i].clear();
		lst[i] = 0;
	}
	
	n = rd(), k = rd();
	For(i,1,n){
		a[i] = rd();
		if(!mp.count(a[i])) mp[a[i]] = ++tot;
		a[i] = mp[a[i]];
	}
	P leijia = {0,0};
	g[leijia].emplace_back(0);
	For(i,1,n){
		leijia = leijia + H.to(a[i]);
		buc[a[i]].emplace_back(i);
		lst[i] = lst[i-1];
		if(buc[a[i]].size()%k==0){
			leijia = leijia - k*H.to(a[i]);
		}
		if((int)buc[a[i]].size()>k){
			lst[i] = max(buc[a[i]][ buc[a[i]].size()-k-1 ], lst[i]);
		}
		g[leijia].emplace_back(i);
	}
	for(auto p:g) calc(p.y);
	printf("%lld\n",ans);
}

bool ED;
signed main(){
	cerr<<abs(&ST-&ED)/1024./1024.<<"MB\n";
	freopen("count.in","r",stdin);
	freopen("count.out","w",stdout);
	int T=rd();double Tim=clock();
	H.init();while(T--) Solve();
	return cerr<<"TIME:"<<(clock()-Tim)/CLOCKS_PER_SEC,0;
}

文章作者: WolfDeer
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 WolfDeer !
  目录