专栏文章

题解:CF2056D Unique Median

CF2056D题解参与者 2已保存评论 1

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
1 条
当前快照
1 份
快照标识符
@miqgeqi9
此快照首次捕获于
2025/12/04 04:22
3 个月前
此快照最后确认于
2025/12/04 04:22
3 个月前
查看原文
首先,对于所有长度为奇数的子串,一定是好的子串,我们可以直接预处理出来。
对于长度为偶数的子串,我们可以观察到 aa 的数据范围只有 1010,一个暴力的思想就是直接枚举子串的中位数 ii,然后统计子串个数。
我们假设一个子串小于 ii 的数的个数为 aa,等于 ii 的数的个数为 bb 大于 ii 的数的个数为 cc,那么一个好的子串需要满足: (a+b+c)2a+b\frac{(a+b+c)}{2} \le a+b a<(a+b+c)2a<\frac{(a+b+c)}{2}
我们将式子拆开,发现 aabbcc 并不好处理,也很难统计,所以我们可以换个思路,应用容斥思想,统计以 ii 为其中一个中位数但不是好子串的个数。
假设,有 aa 个数小于等于 iibb 个数大于 ii,对于我们要统计的两个条件:
  1. ii 为其中一个中位数:ii 要在子串中出现过,且 (a+b)2a\frac{(a+b)}{2} \le a
  2. 这个子串不是好子串,也就是排序后第 (a+b)/2+1(a+b)/2+1 为不等于 iia<(a+b)2+1a<\frac{(a+b)}{2}+1
这个式子列出来,我们就可以发现,其实我们要统计的子串就是小于等于 ii 的数出现恰好 (a+b)2\frac{(a+b)}{2} 次且 ii 出现过的子串。
我们可以借助前缀和和桶统计答案。
最后,ACcode:
CPP
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 200010
#define N 100000
// By flyfreemrn
inline ll read(){
	ll x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		x=x*10+c-'0';
		c=getchar();
	}
	return x*f;
}
inline void write(ll x){
	if(x>9)write(x/10);
	putchar(x%10+'0');
}
ll T,n,a[MAXN],t[MAXN],ans;
ll work(ll p){
//	memset(t,0,sizeof(t)); 不能直接清空桶,会TLE
	ll now=N,res=0;//赋初始值N,防止now<0时越界
	queue<ll> q,_q;//q暂存桶数据,_q负责清空桶
	q.push(N);
	for(int i=1;i<=n;i++){
		if(a[i]<=p)now++;
		else now--;//前缀和统计当前位置小于等于i的数和大于i的数的数量关系
		if(a[i]==p){
			while(!q.empty()){//发现和p相同的数,这时队列中的数据可以添加到桶中在之后被查询
				ll fnt=q.front();
				_q.push(fnt);
				q.pop();
				t[fnt]++;
			}
		}
		res+=t[now];
	 	q.push(now);//因为我们要保证子串存在p,所以要暂存数据
	}
	while(!_q.empty()){//清空桶
		ll fnt=_q.front();
		_q.pop();
		t[fnt]=0;
	}
	return res;
}
int main(){
	T=read();
	while(T--){
		n=read();
		ans=0;
		for(int i=1;i<=n;i++)a[i]=read(),ans+=i;//处理所有子串
		for(int i=1;i<=10;i++){
			ans-=work(i);//容斥
//			cout<<i<<" "<<work(i)<<endl;
		}
		cout<<ans<<endl;
	}
	return 0;
}

评论

1 条评论,欢迎与作者交流。

正在加载评论...