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] 这些数的权值+1add(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;
}