题面传送门

题意:

给出一个序列 \(a\),求 \(a\) 有多少个子区间 \([l,r]\),满足这个区间中出现次数最多的数出现次数 \(>\dfrac{r-l+1}{2}\)

\(1 \leq n \leq 5\times 10^5\)

首先肯定要枚举出现次数最多的数是什么,假设为 \(x\)。

记序列中为 \(x\) 的数为 \(+1\),数列中不为 \(x\) 的数为 \(-1\),那么 \(x\) 出现次数 \(>\dfrac{r-l+1}{2}\) 等价于该区间中对应的数的和 \(>0\)。

考虑对这个 \(+1,-1\) 的序列做一遍前缀和得到 \(s_i\),那么满足条件的区间个数即为 \(s_i\) 的顺序对个数

对于 \(type=1,3\) 的情况,做 \(8\) 次树状数组求顺序对就可以了。

然而对于原题来说这样肯定是不行的,不过发现对于 \(x\) 取什么值,\(1\) 的个数加起来只有 \(n\) 个,这意味着大部分数都是 \(-1\),那么我们思考能不能拿这个性质做文章呢?

考虑从左到右依次插入一段连续的 \(-1\),显然这些位置的 \(s\) 值可以形成一段连续的区间(公差为 \(-1\) 的等差数列),不妨设其为 \([L,R]\)。

假设 \(cnt_j\) 为当前 \(s_i=j\) 的 \(i\) 的个数,那么这段区间的贡献就是

\[\sum\limits_{i=L}^{R}\sum\limits_{j=-\infty}^{i-1}cnt_j
\]

把这个式子稍微调整一下就可以得到

\[(R-L+1)\times\sum\limits_{j=-\infty}^{L-1}cnt_j+\sum\limits_{j=L}^Rcnt_j\times(R-j)
\]

是不是感觉有亿点点可维护?

线段树维护 \(cnt_j\) 的值,支持区间加、求区间 \(cnt_i\) 的和,以及区间 \(i \times cnt_i\) 的和,就可以在 \(\mathcal O(\log n)\) 的时间内求出上面那个式子的值。

由于 \(s_j\) 可能 \(<0\),所以下标要整体加上一个值。

虽然 \(-1\) 的个数很多,但是连续的 \(-1\) 段的个数是 \(\mathcal O(n)\) 级别的,而我们恰好利用了这个性质将复杂度降了下来。

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
typedef pair<int,int> pii;
typedef long long ll;
const int MAXN=5e5+5;
int n,a[MAXN];
vector<int> v[MAXN];
struct node{
int l,r;
ll val,mul,lz,sumi;
} s[MAXN<<4];
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;s[k].val=s[k].lz=s[k].mul=0;if(l==r){s[k].sumi=l-MAXN;return;}
int mid=(l+r)>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
s[k].sumi=s[k<<1].sumi+s[k<<1|1].sumi;
}
void pushdown(int k){
if(s[k].lz){
s[k<<1].val+=(s[k<<1].r-s[k<<1].l+1)*s[k].lz;
s[k<<1].mul+=s[k<<1].sumi*s[k].lz;s[k<<1].lz+=s[k].lz;
s[k<<1|1].val+=(s[k<<1|1].r-s[k<<1|1].l+1)*s[k].lz;
s[k<<1|1].mul+=s[k<<1|1].sumi*s[k].lz;s[k<<1|1].lz+=s[k].lz;
s[k].lz=0;
}
}
void modify(int k,int l,int r,int x){
if(l<=s[k].l&&s[k].r<=r){
s[k].val+=(s[k].r-s[k].l+1)*x;
s[k].mul+=s[k].sumi*x;s[k].lz+=x;
return;
} pushdown(k);
int mid=(s[k].l+s[k].r)>>1;
if(r<=mid) modify(k<<1,l,r,x);
else if(l>mid) modify(k<<1|1,l,r,x);
else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
s[k].val=s[k<<1].val+s[k<<1|1].val;
s[k].mul=s[k<<1].mul+s[k<<1|1].mul;
}
ll query(int k,int l,int r){
// printf("%d %d %d\n",k,l,r);
if(l<=s[k].l&&s[k].r<=r) return s[k].val;
pushdown(k);int mid=(s[k].l+s[k].r)>>1;
if(r<=mid) return query(k<<1,l,r);
else if(l>mid) return query(k<<1|1,l,r);
else return query(k<<1,l,mid)+query(k<<1|1,mid+1,r);
}
ll queryi(int k,int l,int r){
if(l<=s[k].l&&s[k].r<=r) return s[k].mul;
pushdown(k);int mid=(s[k].l+s[k].r)>>1;
if(r<=mid) return queryi(k<<1,l,r);
else if(l>mid) return queryi(k<<1|1,l,r);
else return queryi(k<<1,l,mid)+queryi(k<<1|1,mid+1,r);
}
int main(){
int qwq;scanf("%d%d",&n,&qwq);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),v[a[i]].pb(i);
build(1,0,MAXN<<1);ll ret=0;
for(int i=0;i<n;i++){
// printf("%d\n",i);
int pre=0,sum=0;modify(1,MAXN,MAXN,1);
for(int j=0;j<v[i].size();j++){
int cur=v[i][j];
if(pre+1!=cur){
int r=sum-1,l=sum-(cur-pre-1);
ret+=r*query(1,l+MAXN,r+MAXN)-queryi(1,l+MAXN,r+MAXN);
ret+=(r-l+1)*query(1,0,l-1+MAXN);modify(1,l+MAXN,r+MAXN,1);
sum-=(cur-pre-1);
}
sum++;ret+=query(1,0,sum-1+MAXN);modify(1,sum+MAXN,sum+MAXN,1);
pre=cur;
}
if(pre!=n){
int r=sum-1,l=sum-(n-pre);
ret+=r*query(1,l+MAXN,r+MAXN)-queryi(1,l+MAXN,r+MAXN);
ret+=(r-l+1)*query(1,0,l-1+MAXN);modify(1,l+MAXN,r+MAXN,1);
sum-=(n-pre);
}
pre=0,sum=0;modify(1,MAXN,MAXN,-1);
for(int j=0;j<v[i].size();j++){
int cur=v[i][j];
if(pre+1!=cur){
int r=sum-1,l=sum-(cur-pre-1);
modify(1,l+MAXN,r+MAXN,-1);
sum-=(cur-pre-1);
}
sum++;modify(1,sum+MAXN,sum+MAXN,-1);
pre=cur;
}
if(pre!=n){
int r=sum-1,l=sum-(n-pre);
modify(1,l+MAXN,r+MAXN,-1);
sum-=(n-pre);
}
}
printf("%lld\n",ret);
return 0;
}

upd on 2020.12.4:

考场上想的做法竟然过了!incredible!我还以为它过不了呢/xyx

讲一个 \(n\sqrt{n\log n}\) 的做法。

考虑分块,设一个临界值 \(B\)。

对于每个出现次数 \(\leq B\) 的数,显然它只能对长度 \(<2B\) 的区间产生贡献,枚举每个长度 \(<2B\) 的区间。

对于每个出现次数 \(>B\) 的数,这样的数顶多 \(\dfrac{n}{B}\) 个,对于每一个这样的数搞一遍树状数组求顺序对。

时间复杂度 \(2nB+\dfrac{n^2\log n}{B}\),根据均值不等式可以算得复杂度最优为 \(n\sqrt{n\log n}\)。

woc 这玩意儿真的 nb \(10^9\) 左右给我跑过去了。

顺便提一句:考场上我在求 \(<2B\) 的区间的贡献的时候用了 memset 所以 T 掉了还以为是算法本身的锅。

#include <bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
typedef long long ll;
int n,sub,a[MAXN];
const int BLK=800;
int cnt[MAXN],f[MAXN];
int sum[MAXN];
int bit[MAXN<<1];
void add(int x,int v){for(int i=x;i<MAXN+MAXN;i+=(i&(-i))) bit[i]+=v;}
int query(int x){int ret=0;for(int i=x;i;i-=(i&(-i))) ret+=bit[i];return ret;}
int solve(){
for(int i=1;i<=n;i++) f[a[i]]++;
ll ret=0;
for(int i=1;i<=n;i++){
int mx=0,pos=0;
for(int j=i;j<=n&&j<=i+BLK*2;j++){
cnt[a[j]]++;if(cnt[a[j]]>mx) mx=cnt[a[j]],pos=a[j];
if(mx>(j-i+1)/2&&f[pos]<BLK) ret++;
}
for(int j=i;j<=n&&j<=i+BLK*2;j++){
cnt[a[j]]--;
}
}
for(int i=0;i<n;i++){
if(f[i]<BLK) continue;
memset(sum,0,sizeof(sum));
for(int j=1;j<=n;j++){
if(a[j]==i) sum[j]=sum[j-1]+1;
else sum[j]=sum[j-1]-1;
}
memset(bit,0,sizeof(bit));
add(MAXN,1);
for(int j=1;j<=n;j++){
ret+=query(sum[j]-1+MAXN);
add(sum[j]+MAXN,1);
}
}
printf("%lld\n",ret);
return 0;
}
int main(){
scanf("%d%d",&n,&sub);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
return solve();
}

终于把自爆的心头之恨化解掉了,爽

洛谷 P4062 - [Code+#1]Yazid 的新生舞会(权值线段树)的更多相关文章

  1. 洛谷 P4062 - [Code+#1]Yazid 的新生舞会 的线性做法

    洛谷题面传送门 一个线性做法. \(n\log n\) 解法可以戳这里查看 首先回顾一下 \(n\log n\) 解法的过程:我们对于每一个数 \(x\),考察其出现位置,设为 \(t_1,t_2,t ...

  2. luogu P4062 [Code+#1]Yazid 的新生舞会(线段树+套路)

    今天原来是平安夜啊 感觉这题是道好题. 一个套路枚举权值\(x\),把权值等于\(x\)的设为1,不等于的设为-1,然后问题转化为多少个区间权值和大于. 发现并不是很好做,还有一个套路,用前缀和查分来 ...

  3. P4062 [Code+#1]Yazid 的新生舞会

    思路:分治 提交:2次 错因:数组开小 题解: 我们枚举一下众数\(x\). 设\(s[n]=\sum_{i=1}^n [a[i]==x]\) 那么对于区间\((l,r]\),有\(s[r]-s[l] ...

  4. 「BZOJ2733」「洛谷3224」「HNOI2012」永无乡【线段树合并】

    题目链接 [洛谷] 题解 很明显是要用线段树合并的. 对于当前的每一个连通块都建立一个权值线段树. 权值线段树处理操作中的\(k\)大的问题. 如果需要合并,那么就线段树暴力合并,时间复杂度是\(nl ...

  5. 洛谷P1908 逆序对 [权值线段树]

    题目传送门 逆序对 题目描述 猫猫TOM和小老鼠JERRY最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计.最近,TOM老猫查阅到一个人类称之为“逆序对”的 ...

  6. 【线段树】【P4062】 [Code+#1]Yazid 的新生舞会

    Description 给定一个长度为 \(n\) 的序列,求有多少子区间满足区间众数严格大于区间长度的一半.如果区间有多个出现次数最多且不同的数则取较小的数为众数. Limitation 对于全部的 ...

  7. BZOJ5291/洛谷P4458/LOJ#2512 [Bjoi2018]链上二次求和 线段树

    原文链接http://www.cnblogs.com/zhouzhendong/p/9031130.html 题目传送门 - LOJ#2512 题目传送门 - 洛谷P4458 题目传送门 - BZOJ ...

  8. 【洛谷4770】 [NOI2018]你的名字(SAM,线段树合并)

    传送门 洛谷 Solution 做过的比较玄学的后缀自动机. 果然就像\(Tham\)所讲,后缀自动机这种东西考场考了不可能做的出来的... 考虑如果\(l=1,r=|S|\)的怎么做? 直接建后缀自 ...

  9. [题解] [Code+#1]Yazid 的新生舞会

    题面 题解 upd : \(cnt_i\) 代表值为 \(i\) 的个数 我们可以暴力枚举众数 \(k\) 把等于 \(k\) 的赋值成 1 , 不等于 \(k\) 的赋值成 -1 这样原序列就变成了 ...

随机推荐

  1. 华为在HDC2021发布全新HMS Core 6 宣布跨OS能力开放

    [2021年10月22日·东莞]华为开发者大会 2021(Together)于今天正式开幕,华为在主题演讲中正式发布全新的HMS Core 6,向全球开发者开放7大领域的69个Kit和21,738个A ...

  2. Less-(38~41) 堆叠注入

    首先申明,Less-(38~41)可以采取和Less-(1~4)相同的解法:(一一对应) 然而,他们的漏洞其实更大,我们可以做更多具有破坏性的事情. 代码审计: Less-(38~41): 41的$s ...

  3. 力扣 - 剑指 Offer 53 - II. 0~n-1中缺失的数字

    题目 剑指 Offer 53 - II. 0-n-1中缺失的数字 思路1 排序数组找数字使用二分法 通过题目,我们可以得到一个规律: 如果数组的索引值和该位置的值相等,说明还未缺失数字 一旦不相等了, ...

  4. Java版人脸检测详解上篇:运行环境的Docker镜像(CentOS+JDK+OpenCV)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. 第2次 Beta Scrum Meeting

    本次会议为Beta阶段第2次Scrum Meeting会议 会议概要 会议时间:2021年5月31日 会议地点:「腾讯会议」线上进行 会议时长:0.5小时 会议内容简介:对完成工作进行阶段性汇报:对下 ...

  6. SpringBoot加密配置属性

    一.背景 在系统中的运行过程中,存在很多的配置属性,比如: 数据库配置.阿里云配置 等等,这些配置有些属性是比较敏感的,是不应直接以明文的方式出现在配置文件中,因此对于这些配置我们就需要加密来处理. ...

  7. UVA-1016 Silly Sort

    UVA-1016 题目大意:给定一个长度为n的序列,每次操作可以交换任意两个数的位置,代价为两个数的和,求最小代价,将序列排成有序的. 首先,显然需要交换的数一定会形成环: 那么对于每一个环,我们有两 ...

  8. 计算机网络传输层之TCP拥塞控制(慢开始与拥塞避免、快重传和快恢复)

    文章转自:https://blog.csdn.net/weixin_43914604/article/details/105532044 学习课程:<2019王道考研计算机网络> 学习目的 ...

  9. Spring---IoC(控制反转)原理学习笔记【全】

    1.IoC创建对象的方式 使用无参构造创建对象 假如要使用有参构造创建: 下标赋值constructor-arg <!--有参--> <bean id="User" ...

  10. c#复制数组的多种方法

    方法一:使用for循环 int []pins = {9,3,7,2} int []copy = new int[pins.length]; for(int i =0;i!=copy.length;i+ ...