把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷7125】[Ynoi2008] rsmemq(根号分治)

题目链接

  • 给定一个长度为 \(n\) 的整数序列 \(a\)
  • 定义一个区间 \([l,r]\) 是优秀的,当且仅当 \(\frac{l+r}2\)\([l,r]\) 的众数。
  • \(q\) 次询问,每次给定一个区间,询问它有多少个子区间是优秀的。
  • \(1\le n,q\le5\times10^5\)

枚举中点+根号分治

容易想到去枚举 \(x=\frac{l+r}2\),这样一来只要判断它是不是以它为中心的区间的众数。

考虑将所有 \(a_i=x\) 的数按照与 \(x\) 的距离 \(|i-x|\) 排序,显然越前面的数会越早进入以 \(x\) 为中心的区间。

而当区间内 \(x\) 的个数确定时,优秀的区间长度肯定是一段连续范围,所以可以不断加入数然后通过二分求出优秀区间的最大长度。

问题在于如何检验,这被转化成了求区间众数个数,显然不可能直接套板子每次 \(O(\sqrt n)\) 做。

实际上,我们可以根号分治。

对于 \(a_i=x\)\(i\) 的个数大于 \(\sqrt n\)\(x\),暴力以 \(x\) 为中心不断向外扩展,维护每种数的出现次数即可。

而对于个数小于等于 \(\sqrt n\)\(x\),我们每次需要检验一个区间内众数的个数是否小于等于某个不超过 \(\sqrt n\) 的数。

可以针对每种个数双指针扫一遍,求出每个左端点在保证区间内每种数的个数不超过限定值的前提下的最大右端点。则检验区间 \([x-u,x+u]\) 时就只需判断 \(x-u\) 的最大右端点是否大于等于 \(x+u\) 即可。

注意为了避免被卡内存,这里需要先枚举个数,双指针扫一遍求出最大右端点,然后针对每个 \(x\) 处理这种个数时的情况。

分类讨论+树状数组

上面的贡献形如一个三元组 \((x,l,r)\),表示对 \(i\in[l,r]\),区间 \([x-i,x+i]\) 是合法的。

对于询问 \([L,R]\),若 \(x\le \frac{L+R}2\) 则只需要考虑 \(L\le x-i\) 这个限制,同理若 \(x >\frac{L+R}2\) 则只需要考虑 \(R\ge x+i\) 这个限制。

所以先将三元组按照 \(x\) 排序,询问按照 \(\frac{L+R}2\) 排序。根据三元组在当前询问的 \(\frac{L+R}2\) 哪一侧用两个树状数组分别维护,询问时在两个树状数组中分别用 \(L\)\(R\) 询问。

\(x\le\frac{L+R}2\) 时的贡献为例,需要给 \([1,x-r]\) 中的 \(L\) 加上 \(r-l+1\)\((x-r,x-l]\) 中的 \(L\) 加上 \((x-l)-L+1\),也就是区间加一次函数。只要分别维护斜率和以及截距和即可。

代码:\(O(n\sqrt n+q\log n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 500000
#define LL long long
using namespace std;
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
int n,B,a[N+5],c[N+5],w[N+5],w_[N+5],lim[N+5];
int Qt;LL ans[N+5];struct Q {int p,l,r;bool operator < (Cn Q& o) Cn {return l+r<o.l+o.r;}}q[N+5];
int tt;struct OP {int x,l,r;bool operator < (Cn OP& o) Cn {return x<o.x;}}p[2*N+5];
int m;vector<int> g[N+5];bool cmp(CI x,CI y) {return abs(x-m)<=abs(y-m);}
struct TreeArray//树状数组
{
	int k[N+5];LL b[N+5];
	I void U(RI x,RI K,RI B) {W(x) k[x]+=K,b[x]+=B,x-=x&-x;}
	I LL Q(CI o) {RI x=o,K=0;LL B=0;W(x<=n) K+=k[x],B+=b[x],x+=x&-x;return 1LL*K*o+B;}
	I void U1(CI l,CI r,CI v) {U(r,-v,(r+1)*v),U(l,v,-l*v);}//第一类修改
	I void U2(CI l,CI r,CI v) {U(n,0,(r-l+1)*v),U(r,v,-r*v),U(l-1,-v,(l-1)*v);}//第二类修改
}T1,T2;
I void BF(CI x)//暴力
{
	RI i,mx=0,L=-1,R;for(i=1;i<=n;++i) c[i]=0;for(i=0;1<=x-i&&x+i<=n;++i)//以x为中心向两侧扩展
		mx=max(mx,++c[a[x-i]]),i&&(mx=max(mx,++c[a[x+i]])),c[x]<mx?~L&&(p[++tt]=(OP){x,L,R},L=-1):(!~L&&(L=i),R=i);//mx记录众数个数
	~L&&(p[++tt]=(OP){x,L,R},0);
}
int main()
{
	RI i,j;for(read(n,Qt),B=sqrt(n),i=1;i<=n;++i) read(a[i]),g[a[i]].push_back(i);
	RI ct=0;for(i=1;i<=n;++i) if(m=i,sort(g[i].begin(),g[i].end(),cmp),g[i].size()>B) BF(i);else if(!g[i].empty()) w[++ct]=i;//根号分治
	RI x,t,nt,l,r,u;for(RI k=1;k<=B;++k)//枚举个数
	{
		for(t=0,i=1;i<=n;++i) c[i]=0;
		for(i=1,j=0;i<=n;++i) {W(j^n&&!t) ++c[a[++j]]>k&&(t=1);t?(lim[i]=j-1,--c[a[i]]==k&&(t=0)):lim[i]=n;}//双指针求最大右端点
		for(nt=0,i=1;i<=ct;++i)
		{
			if(g[x=w[i]].size()<k||abs(g[x][k-1]-x)>min(n-x,x-1)) continue;//不可能达到k个
			w_[++nt]=x;if(abs(g[x][k-1]-x)==abs(g[x][k]-x)) continue;//如果同时会进入两个,这种数不可能有k个
			l=abs(g[x][k-1]-x)-1,r=g[x].size()>k?min(abs(g[x][k]-x),min(n-x,x-1)):min(n-x,x-1);//注意二分边界
			W(l^r) u=l+r+1>>1,lim[x-u]>=x+u?l=u:r=u-1;l>=abs(g[x][k-1]-x)&&(p[++tt]=(OP){x,abs(g[x][k-1]-x),l},0);//二分答案
		}
		for(ct=nt,i=1;i<=ct;++i) w[i]=w_[i];
	}
	for(i=1;i<=Qt;++i) read(q[i].l,q[i].r),q[i].p=i;sort(q+1,q+Qt+1),sort(p+1,p+tt+1);
	for(i=1;i<=tt;++i) T2.U2(p[i].x+p[i].l,p[i].x+p[i].r,1);//初始全在询问区间中点右侧
	for(i=j=1;i<=Qt;++i)
	{
		W(j<=tt&&2*p[j].x<q[i].l+q[i].r) T1.U1(p[j].x-p[j].r,p[j].x-p[j].l,1),T2.U2(p[j].x+p[j].l,p[j].x+p[j].r,-1),++j;//调整三元组所处类型
		ans[q[i].p]=T1.Q(q[i].l)+T2.Q(q[i].r);//在两个树状数组中分别用左右端点询问
	}
	for(i=1;i<=Qt;++i) writeln(ans[i]);return clear(),0;
}
posted @ 2022-04-19 21:01  TheLostWeak  阅读(84)  评论(0编辑  收藏  举报