洛谷 P4062 - [Code+#1]Yazid 的新生舞會(權值線段樹)


題面傳送門

題意:
給出一個序列 \(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();
}

終於把自爆的心頭之恨化解掉了,爽


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM