P4062 Yazid的新生舞会(树状数组)

Yazid的新生舞会

题目描述

Yazid 有一个长度为 n 的序列 A,下标从 1n。显然地,这个序列共有 n(n+1)2 个子区间。

对于任意一个子区间 [l,r],如果该子区间内的众数在该子区间的出现次数严格大于 rl+12(即该子区间长度的一半),那么 Yazid 就说这个子区间是“新生舞会的”。

所谓众数,即为该子区间内出现次数最多的数。特别地,如果出现次数最多的数有多个,我们规定值最小的数为众数。

现在,Yazid 想知道,共有多少个子区间是“新生舞会的”。

思路:

  如果一个数是当前区间的众数,那一定会满足cntrcntl>rl(cntrcntl)移相后可转化为2cntrr>2cntll这样子就将原问题转化成了对每个r有多少个l[0,r1]满足2cntrr>2cntll,可以直接用树状数组来维护,但是直接做的时间复杂度是O(n2logn),所以需要进一步去优化.
  令Pi=2cntii,可以发现Pi在上一次出现所选众数x的位置和下一次出现x的位置之间,是一个公差为1的等差数列,那么每出现一次x就相当于给后面的区间加上一个公差为1的等差数列.那么用ci来存储权值,用Ti=j=1icj来表示ci的前缀和,每一个Pi,它对区间的贡献就是TPi1.那么对区间[l,r]的贡献就是i=l1r1Ti,记Gi=j=1iTj来表示Ti的前缀和,所以总共的贡献就是Gr1Gl1。这样就转化为了一个区间加和二维前缀和的操作。
  cx=i=1xbi=i=1xj=1iaj=i=1xj=1ik=1jdk
  就将二维前缀和转化成了差分数组dk的三维前缀和,因为i=1xj=1ik=1jdk这个式子太过于繁琐要推导出一个能够用树状数组直接计算的式子.
  i=1xj=1ik=1jdk=i=1xk=1i(ik+1)dk=i=1xk=1ii×dki=1xk=1i(k1)dk
  对于i=1xk=1ii×ak展开为1×(a1)+2×(a1+a2)+(x1)(a1+a2+ax1)+x×(a1+a2++ax1+ax)再合并一下同类项,(1+2++x)a1+(2+3++x1+x)a2++axai前的系数是一个等差数列,可以求和再次化简, ((1+x)x2a1+(k+x)(xk+1)2++ax),最终转化为k=1x(k+x)(xk+1)2dk
  对于i=1xk=1i(k1)dk展开为0×a1+(21)(x1)×a2+(31)(x2)a3++(k1)(xk+1)ak++(x1)(xx+1)ax,合并为k=1x(k1)(xk+1)dk
  i=1xk=1ii×dki=1xk=1i(k1)dk=k=1x(k+x)(xk+1)2dkk=1x(k1)(xk+1)dk=k=1x(x+2k)(x+1k)2dk=(x+2)(x+1)2k=1xdk2x+32k=1xdkk+12k=1xdkk2
  只需要用树状数组分别维护dk,kdk,k2dk就行了。

int n; i64 c1[N * 2], c2[N * 2], c3[N * 2]; void add(int x, i64 v) { for (int i = x; i <= n * 2 + 1; i += i & -i) { c1[i] += v; c2[i] += 1ll * v * x; c3[i] += 1ll * v * x * x; } } i64 query(int x) { i64 ans = 0; for (int i = x; i; i -= i & -i) { ans += c1[i] * (x + 2) * (x + 1) - c2[i] * (2ll * x + 3) + c3[i]; } return ans / 2; } int a[N]; std::vector<int> b[N]; signed main() { int op; scanf("%d%d", &n, &op); for (register int i = 1; i <= n; i++) { scanf("%d", a + i); b[a[i]].push_back(i); } i64 ans = 0; for (register int i = 0; i < n; i++) { int last = 0; b[i].push_back(n + 1); for (int j = 0; j < int(b[i].size()); j++) { int r = n + 1 + 2 * j - last, l = n + 1 + 2 * j - (b[i][j] - 1); ans += query(r - 1) - (l >= 3 ? query(l - 2) : 0); add(l, 1); add(r + 1, -1); last = b[i][j]; } last = 0; for (register int j = 0; j < int(b[i].size()); j++) { int l = n + 1 + 2 * j - (b[i][j] - 1), r = n + 1 + 2 * j - last; add(l, -1), add(r + 1, 1); last = b[i][j]; } } printf("%lld\n", ans); return 0 ^ 0; }

__EOF__

本文作者HoneyGrey
本文链接https://www.cnblogs.com/Haven-/p/16737340.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   浅渊  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示