P8313 [COCI2021-2022#4] Izbori

· · 题解

\text{Links}

原题传送门

可能更好的阅读体验

题意

求给定序列中有多少个子区间满足众数出现次数严格大于区间长度的一半。

题解

题目要求满足条件的子区间,一个很直接的想法是每次固定左(右)端点,求有多少个右(左)可以与其匹配对答案造成贡献。

那么考虑一个暴力做法:每次固定左端点,然后往后面一直扫,枚举每个右端点,中途记录众数出现次数,然后依次判断即可。时间复杂度为 O(n^2)

这肯定是过不了的,那么我们再从条件入手,注意到:

严格大于区间长度的一半

于是就说明每个区间对应的这个众数只会有一个!考虑利用一下这个性质。

那么我们可以枚举这个众数。设我们当前枚举的众数为 x,记录一个数组 ss_i 表示前 i 个数中 x 的出现次数。

沿用上面固定一个端点求另一个合法端点数量的思路,对于一个右端点 r,合法的左端点 l 显然应该满足:

l \le r,s_r-s_{l-1}\gt \frac{r-(l-1)}{2} $$ l\lt r ,s_r-s_l\gt \frac{r-l}{2} $$ 不等式两边同时 $\times 2$: $$ l\lt r,2s_r-2s_l\gt r-l $$ 移项得: $$ l\lt r,2s_r-r\gt 2s_l-l $$ 记 $s'_i=2s_i-i$,则: $$ l\lt r,s'_r\gt s'_l $$ 经典问题,树状数组维护即可。但时间复杂度为 $O(n^2\log n)$,甚至不如 $O(n^2)$ 暴力(悲。 那么我们考虑整体观察一下序列 $s'$,说不定能发现什么(。 显然地,如果满足 $a_i=x$ 的若干个 $i$ 的的位置都确定了,那么整个 $s'$ 序列就可以确定了,所以我们要想办法只用这些 $i$ 的位置来计算答案,使得每次的时间复杂度都只与 $m$ 相关,其中 $m$ 为这些 $i$ 的数量。再因为 $\sum m=n$,于是可以大幅降低总时间复杂度。 记 $d$ 为 $s'$ 的差分序列,那么如果 $a_i=x$,则有 $d_i=1$,否则 $d_i=-1$,我们把前者中的 $i$ 视为一个“**断点**”,那么整个 $s'$ 序列就是若干个公差为 $-1$ 的**等差数列**“首尾衔接”拼在一起。 因为公差为 $-1$,即单调递减,所以每一个等差数列内部是不会产生任何贡献的,那么我们考虑把每个等差数列视作整体,看它前面的等差数列可以产生多少贡献。 沿用上面树状数组的做法,开一个**桶** $t$,$t_i$ 表示 $s'$ 值为 $i$ 的位置有多少个,因为每次要查询小于某个值的数量,所以把它记成前缀和的形式,记 $v$ 为它的**前缀和序列**。那么对于每个右端点 $r$,它的答案显然是 $v_{s'_r-1}$。我们把每一段等差数列的贡献写下来就是: $$ \sum_{i=fir}^{lst}v_{i-1} $$ 然后它又可以写成两个前缀和相减的形式,也就成了 $t$ 序列的**二阶前缀和**,那么我们就需要在这个桶上面实现:区间加(插入一个等差数列),维护二阶前缀和。 ~~呵,果然又变成大力 ds 了是吧~~ 线段树和树状数组都可以维护,在我看来各有优势吧,线段树写此题代码的时候比较容易理解,好写一点,但这题树状数组的常数吊打线段树。 这里给出线段树的实现方式: 代码实现中的细节: 1.因为 $s'$ 序列中可能出现负数,所以要加上一定的偏差值给它强制转成正数。 2.离散化 3.long long ```cpp #include<bits/stdc++.h> using namespace std; #define int long long #define il inline #define re register const int N=2e5+113; int n,a[N],b[N],ans,dt,vmax; pii lst[N]; bitset<N>solved; vector<int>v[N]; struct SegT{ int ans,sum,tag,l,r; }L[N<<3]; #define ls (id<<1) #define rs (id<<1|1) il void Pushup(SegT &fa,SegT lson,SegT rson){ fa.sum=lson.sum+rson.sum; int l=lson.l,mid=lson.r,r=rson.r; fa.ans=lson.ans+rson.ans+lson.sum*(r-mid); } il int Get(int x){ return (x*(x+1))>>1; } il void Add(SegT &fa,int x){ fa.tag+=x,fa.sum+=(fa.r-fa.l+1)*x,fa.ans+=Get(fa.r-fa.l+1)*x; } il void Pushdown(SegT &fa,SegT &lson,SegT &rson){ if(!fa.tag)return; Add(lson,fa.tag),Add(rson,fa.tag); fa.tag=0; } il void Add(int id,int l,int r,int x,int y,int z){ if(l>=x&&r<=y){ Add(L[id],z); return; } Pushdown(L[id],L[ls],L[rs]); int mid=(l+r)>>1; if(x<=mid)Add(ls,l,mid,x,y,z); if(y>mid)Add(rs,mid+1,r,x,y,z); Pushup(L[id],L[ls],L[rs]); } il SegT GetAns(int id,int l,int r,int x,int y){ if(l>=x&&r<=y)return L[id]; Pushdown(L[id],L[ls],L[rs]); int mid=(l+r)>>1; SegT res; if(x<=mid&&y>mid){ SegT Left=GetAns(ls,l,mid,x,y); SegT Right=GetAns(rs,mid+1,r,x,y); Pushup(res,Left,Right); } else if(x<=mid)res=GetAns(ls,l,mid,x,y); else res=GetAns(rs,mid+1,r,x,y); Pushup(L[id],L[ls],L[rs]); return res; } il void Build(int id,int l,int r){ L[id]={0,0,0,l,r}; if(l==r)return; int mid=(l+r)>>1; Build(ls,l,mid),Build(rs,mid+1,r); } il void solve(int x){ solved[x]=1; int siz=v[x].size()-1,mx=dt,mn=dt; for(re int i=0;i<=siz;i++) b[i]=(i<<1)-v[x][i]+dt,mx=max(mx,b[i]),mn=min(mn,b[i]); for(re int i=0;i<=siz;i++){ int r=b[i],l=(i==siz)?b[i]-(n-v[x][i]):b[i]-(v[x][i+1]-v[x][i]-1); ans+=GetAns(1,1,vmax,1,r-1).ans-GetAns(1,1,vmax,1,l-2).ans; Add(1,1,vmax,l,r,1); } for(re int i=0;i<=siz;i++){ int r=b[i],l=(i==siz)?b[i]-(n-v[x][i]):b[i]-(v[x][i+1]-v[x][i]-1); Add(1,1,vmax,l,r,-1); } } il int read(){ re int x=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar(); return x*f; } signed main(){ n=read(),a[0]=read(); dt=n+10,vmax=n+dt; for(re int i=1;i<=n;i++){ a[i]=read(); if(v[a[i]].empty())v[a[i]].push_back(0); v[a[i]].push_back(i); } Build(1,1,vmax); for(re int i=1;i<=n;i++) if(!solved[a[i]])solve(a[i]); cout<<ans; return 0; } ```