Codeforces Round 997 (Div. 2) D. Unique Median 讲思路更讲推理过程

74 阅读2分钟

未看懂请放心评论,将在几个小时内进行回复

题目

D. Unique Median

题意

给定一个序列aa,大小为nn,求它的满足bn+12=bn+12b_{\left\lfloor \frac{n + 1}{2} \right\rfloor} = b_{\left\lceil \frac{n + 1}{2} \right\rceil}的子段个数(即上取整中位数和下取整中位数相等的子段个数)

解法

  1. 奇数长度的子段一定满足要求
  2. 我们可以将答案转化为:所有区间的个数-不满足要求的区间个数。
  3. 不满足要求的区间为【。。。xxyy。。。】型的区间。x,yx,y分别为下取整中位数和上取整中位数
  4. 这些不满足要求的区间可以被划分为下取整中位数为1,2,3,4,5...10的共计10种类型。
  5. 所以问题转化为我们怎么求出中位数为kk的不满足要求的区间个数了。
  6. 转化:中位数为k时,意味着小于等于k的数字数量刚好等于大于k的数字数量,所以如果aika_i \leq k我们记作+1,ai>ka_i >k我们记作-1,当一个区间内和为0的时候就意味着仅有下取整中位数为kk
  7. 并且当一个区间仅有下取整中位数为kk的时候,也意味着,长度一定为偶数,因为长度为奇数的时候,一定不会出现区间和为0。
  8. 但是要注意,这个区间是需要满足两个条件的 ①区间和为0 ②区间内包含kk这个数字
  9. 条件①我们可以使用前缀和记录的方式较为轻松的得到,而条件②我们可以将一个区间看成...k1...k2......k3....k4....【...k_1...k_2......k_3....k_4....】这种样子,其中kthk_{th}表示第几个kk,我们在k2k_2k3k_3中间只能使用k2k_2之前求出的前缀值,因为要保证kk的数量>1。所以我们可以每到一个新的kk就更新一下前缀和记录数组。

代码

#include <bits/stdc++.h>
using namespace std;


#define inf64 INT64_MAX/2
#define inf32 INT32_MAX/2
#define ll long long
#define int ll
#define pii pair<int,int>
#define endl '\n'
#define vv vector
#define cy cout<<"Yes"<<endl
#define cn cout<<"No"<<endl
#define its(a)  a.begin(),a.end()
#define minV *min_element
#define maxV *max_element

int _;

void solve() {


	int n; cin >> n;

	vv<int>a(n + 1);
	for (int i = 1; i <= n; i++)
		cin >> a[i];

	auto get=[&](int k) -> int {
		map<int, int>cnt_last;	//合法的前缀和数量记录
		vv<int>Do;				//在这个新增区间内的操作
		int now = 0;			//中位数为k的坏区间的个数
		int s = 0;				//当前的和值
		Do.push_back(s);		//新增的操作
		for (int i = 1; i <= n; i++) {
			if (a[i] == k) {	//将新增的操作加入前缀和记录中
				for (auto a : Do)
					cnt_last[a]++;
				Do.clear();
			}
			if (a[i] <= k)
				s++;
			else
				s--;
			now += cnt_last[s];
			//记录操作
			Do.push_back(s);
		}
		return now;
	};
	int ans = 0;
	for (int i = 1; i <= 10; i++) {
		ans += get(i);
	}
	cout << (n + 1) * n / 2 - ans << endl;
}
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	_ = 1;
	cin >> _;
	while (_--) {
		solve();
	}
	return 0;
}