P4062 [Code+#1]Yazid 的新生舞会(线段树做法)
Posted Jozky86
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P4062 [Code+#1]Yazid 的新生舞会(线段树做法)相关的知识,希望对你有一定的参考价值。
P4062 [Code+#1]Yazid 的新生舞会(线段树做法)
题意:
给你一个序列a[1…n],求存在绝对众数的子区间个数。
绝对众数指:区间中出现次数最多的那个数,出现次数严格大于区间长度的一半。
题解:
这两个博客将的很长清楚明白(尤其是第一个),我在反复看了n遍后,终于明白。题目细节很多,我再怎么写也没这两个详细,干脆直接放上链接。
Zechariah的博客
OMG_wc 的博客
关于题解中提到的三阶前缀和:
三阶前缀和公式转换:
图来自lx_tyin博客
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const LL mod = 1e9 + 7;
const int N = 500005;
// 修改差分 来维护前缀和的前缀和
// c1 为差分d c2为d*i c3 为d*i*i
LL c1[N * 2], c2[N * 2], c3[N * 2];
LL sum(int x) {
LL res = 0;
for (int i = x; i > 0; i -= i & -i) {
res += c1[i] * (x + 2) * (x + 1) - c2[i] * (2 * x + 3) + c3[i];
}
return res / 2;
}
void add(int x, LL d, int n) {
for (int i = x; i <= n; i += i & -i) {
c1[i] += d;
c2[i] += d * x;
c3[i] += d * x * x;
}
}
int a[N];
vector<int> b[N];
int main() {
int n;
scanf("%d%*d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
b[a[i]].push_back(i);
}
const int wc = n + 1; // 偏移量,把[-n,n] 平移到 [1,2n+1]
LL ans = 0;
for (int i = 0; i < n; i++) {
b[i].push_back(n + 1);
int last = 0;
for (int j = 0; j < b[i].size(); j++) {
//j表示i的个数
int y = 2 * j - last + wc;
int x = 2 * j - (b[i][j] - 1) + wc;
// 查询 sum([1,t-1] 的权值和), 其中t在[x,y]范围内,
ans += sum(y - 1) - (x >= 3 ? sum(x - 2) : 0);
// [x,y] 这些数的权值+1
add(x, 1, 2 * n + 1);
add(y + 1, -1, 2 * n + 1);
last = b[i][j];
}
//撤销操作
last = 0;
for (int j = 0; j < b[i].size(); j++) {
int y = 2 * j - last + wc;
int x = 2 * j - (b[i][j] - 1) + wc;
add(x, -1, 2 * n + 1);
add(y + 1, 1, 2 * n + 1);
last = b[i][j];
}
}
printf("%lld\\n", ans);
return 0;
}
以上是关于P4062 [Code+#1]Yazid 的新生舞会(线段树做法)的主要内容,如果未能解决你的问题,请参考以下文章
luogu P4062 [Code+#1]Yazid 的新生舞会(线段树+套路)
洛谷 - P4062 [Code+#1]Yazid 的新生舞会(推公式+线段树)
BZOJ5110[CodePlus2017]Yazid 的新生舞会 线段树