1. 程式人生 > 其它 >【洛谷7125】[Ynoi2008] rsmemq(根號分治)

【洛谷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;
}